Mostrando entradas con la etiqueta Prog_SQL. Mostrar todas las entradas
Mostrando entradas con la etiqueta Prog_SQL. Mostrar todas las entradas

miércoles, 14 de diciembre de 2011

Procedimiento almacenado para encontrar el primer hueco en una tabla

Con este procedimiento almacenado podemos obtener el primer hueco existente en cualquier tabla


CREATE PROCEDURE [dbo].[sp_ObtenerPrimerHueco]
    @table VARCHAR(100),
    @numericColumn VARCHAR(100),
    @minValue INT = NULL,
    --valor obtenido
    @value INT OUT
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @sql AS NVARCHAR(MAX)
    SET @sql =       N' SELECT TOP 1 @value = ' + @numericColumn
    SET @sql = @sql + ' FROM ' + @table + ' t1'
    SET @sql = @sql + ' WHERE NOT EXISTS('
    SET @sql = @sql + '    SELECT ' + @numericColumn
    SET @sql = @sql + '    FROM ' + @table + ' t2'
    SET @sql = @sql + '    WHERE t1.' + @numericColumn + ' + 1 = t2.' + @numericColumn
    SET @sql = @sql + ' )'
    SET @sql = @sql + ' AND ' + @numericColumn + ' >= ' + ISNULL(CAST(@minValue AS VARCHAR), '0')
    SET @sql = @sql + ' ORDER BY ' + @numericColumn     
    
    EXEC sp_executesql @statement = @sql,
         @params = N'@value INT OUTPUT',
         @value = @value OUTPUT 
END

lunes, 12 de julio de 2010

Error Instalando SQL Server 2008: Valor inválido para INSTANCESHAREDWOWDIR

Realizando la instalación de SQL Server 2008, en un PC con Windows Server 2008 64 bits, me he topado con un error que no me deja continuar con la instalación. Dicho error dice que INSTANCESHAREDWOWDIR no tiene un valor correcto.

Tras intentar decenas de cosas, y leer en varios foros, parecía que la solución estaba en ejecutar setup.exe desde la consola de comandos especificando, mediante un parámetro, el valor correcto para éste. Pero no se ha solucionado.

Después de un buen rato, he dado con la solución que me ha permitido instalar SQL Server 2008 en esa máquina:

  • Ejecutar \x64\setup\x64\SQLSysClrTypes.msi (se encuentra en el CD)
  • Borrar esta entrada del registro [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-518\ Components\0D1F366D0FE0E404F8C15EE4F1C15094]
  • Borrar esta entrada del registro [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\ Components\C90BFAC020D87EA46811C836AD3C507F]
Una vez hecho esto, he vuelto a ejecutar la instalación, de forma normal, y todo ha funcionado correctamente. Además, la instalación ahora me ha permitido cambiar la ruta para el parámetro INSTANCESHAREDWOWDIR, aunque no he necesitado cambiarla.


lunes, 29 de marzo de 2010

Introducción a SQL Server Mirroring

Mirroring es una solución software, introducida en SQL Server 2005, que nos permite tener un sistema de base de datos tolerante a fallos, aumentando la seguridad y la disponibilidad, mediante la duplicidad de la base de datos.
La "duplicación" se hace a nivel de base de datos, es decir, no se configura simplemente una instancia para que actúe como "espejo" de otra, si no que hay que crear una base de datos "espejo" por cada base de datos "principal". Hay que crear los mismos usuarios en la base de datos "espejo" (aunque la base de datos "espejo" no está accesible mientras haya otra "principal" activa, ésta será accesible en el momento en el que la instancia principal falle o lo especifiquemos manualmente), así como también los mismos trabajos condicionados para que se ejecuten según si la base de datos está funcionando como "espejo" o como "principal".

Una vez duplicada la base de datos (el registro de transacciones, los usuarios y los trabajos) hay que elegir el modo de funcionamiento en el que usaremos el "mirroring":

- Alta seguridad: Este modo ejecuta en la instancia "espejo" las mismas transacciones que se ejecutan en la instancia "principal". Se realizan de forma síncrona, es decir, por cada transacción ejecutada en la base de datos principal, se comprueba que se ha podido ejecutar en la base de datos espejo. Lo bueno es que no se pierde ninguna transacción, lo malo es que haría algo más lentas las transacciones con lo cual, la aplicación se ejecutaría sensiblemente más lenta.
En este modo el cambio de base de datos, en el caso en el que falle la bd principal, se hace de forma manual. Si se "cae" el servidor espejo la base de datos principal deja de estar activa.

- Alto rendimiento: Este modo ejecuta las transacciones, ejecutadas en la BD principal, en la BD espejo de forma asíncrona. Con esto se consigue mantener la latencia de las transacciones pero hay riesgo de pérdida de datos. El cambio de base de datos si la bd principal falla también se hace de forma manual. Si se cae el servidor espejo, el principal sigue funcionando.

Al modo de alta seguridad hay que sumarle la posibilidad de agregar un tercer servidor llamado "testigo" el cual servirá para poder automatizar la recuperación ante fallos. De esta forma, el modo "alta seguridad se divide en dos":
- Alta protección: alta seguridad sin testigo.
- Alta disponibilidad: alta seguridad con testigo.

Al añadirle un servidor "testigo", además permitimos que, si se cae el servidor espejo, la BD principal siga estando activa.

viernes, 26 de marzo de 2010

Tareas de mantenimiento en SQL Server 2008

A continuación publico 3 scripts que serirán para realizar tareas de mantenimiento sobre nuestras bases de datos en un servidor SQL Server 2008. Estos 3 scripts son para realizar una copia de seguridad, reducir el log de transacciones y regenerar índices. Lo bueno de estos scripts es que no son exclusivos para una base de datos, sino que realizarán las tareas sobre todas las bases de datos que tengas en el servidor (o las que tú quieras).

Copia de base de datos (Debes asignar la variable @backupPath con tu ruta):
DECLARE @backupPath AS VARCHAR(MAX)
SET @backupPath = N'C:\SqlServer\MSSQL10.MSSQLSERVER\MSSQL\Backup\'

DECLARE @dbName AS VARCHAR(100)
DECLARE @file AS VARCHAR(MAX)

DECLARE c1 CURSOR FOR
SELECT name FROM master..sysdatabases sdb
WHERE sdb.name NOT IN ('master','model','msdb','pubs','northwind','tempdb')
ORDER BY name

OPEN c1

FETCH NEXT FROM c1
INTO @dbName

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @file = @backupPath + @dbName + '.BAK'

    BACKUP DATABASE @dbName
    TO  DISK = @file WITH NOFORMAT, INIT,  NAME = @dbName, SKIP, NOREWIND, NOUNLOAD,  STATS = 10

    FETCH NEXT FROM c1
    INTO @dbName
END

CLOSE c1
DEALLOCATE c1
Reducir el log de transacciones
DECLARE @dbName AS VARCHAR(100)
DECLARE @cmd AS VARCHAR(MAX)
DECLARE c1 CURSOR FOR
SELECT name FROM master..sysdatabases sdb
WHERE sdb.name NOT IN ('master','model','msdb','pubs','northwind','tempdb')
ORDER BY name

OPEN c1

FETCH NEXT FROM c1
INTO @dbName

WHILE @@FETCH_STATUS = 0
BEGIN
    --Establecer la base de datos en uso
    SET @cmd = 'USE ' + @dbName

    --Si se establece el modo de restauración a simple, las partes inactivas del log de transacción deben ser borradas
    --Este comando reducirá el archivo de log un poco
    SET @cmd = @cmd + ' ALTER DATABASE ' + @dbName + ' SET RECOVERY SIMPLE'

    --Obtener el nombre de log de la base de datos
    SET @cmd = @cmd + ' DECLARE @logFile AS NVARCHAR(1000)'
    SET @cmd = @cmd + ' SELECT @logFile = name FROM ' + @dbName + '.sys.database_files WHERE type_desc = ''LOG'''
    --Cambiar el modo de restauración a Simple no es suficiente, esto reduce el log a 1 MB
    SET @cmd = @cmd + ' DBCC SHRINKFILE (@logFile , 1)'

    EXEC(@cmd)

    FETCH NEXT FROM c1
    INTO @dbName
END

CLOSE c1
DEALLOCATE c1
Regenerar índices
SET QUOTED_IDENTIFIER ON

DECLARE @Table VARCHAR(255) 
DECLARE @DataBase VARCHAR(255)
DECLARE @cmd NVARCHAR(500) 
DECLARE @fillfactor INT

SET @fillfactor = 90

DECLARE DatabaseCursor CURSOR FOR 
SELECT name FROM master.dbo.sysdatabases  
WHERE name NOT IN ('master','model','msdb','tempdb','distrbution')  
ORDER BY 1 

OPEN DatabaseCursor 

FETCH NEXT FROM DatabaseCursor INTO @Database 
WHILE @@FETCH_STATUS = 0 
BEGIN 

   SET @cmd = 'DECLARE TableCursor CURSOR FOR SELECT table_catalog + ''.'' + table_schema + ''.'' + table_name as tableName  
                    FROM ' + @Database + '.INFORMATION_SCHEMA.TABLES WHERE table_type = ''BASE TABLE'''  

   -- create table cursor 
   EXEC (@cmd) 
   OPEN TableCursor  

   FETCH NEXT FROM TableCursor INTO @Table  
   WHILE @@FETCH_STATUS = 0  
   BEGIN  

       -- SQL 2000 command 
       --DBCC DBREINDEX(@Table,' ',@fillfactor)  
        
       -- SQL 2005 command 
       SET @cmd = 'ALTER INDEX ALL ON ' + @Table + ' REBUILD WITH (FILLFACTOR = ' + CONVERT(VARCHAR(3),@fillfactor) + ')' 
       EXEC (@cmd) 

       FETCH NEXT FROM TableCursor INTO @Table  
   END  

   PRINT @Database + ' indexes were rebuilt'

   CLOSE TableCursor  
   DEALLOCATE TableCursor 

   FETCH NEXT FROM DatabaseCursor INTO @Database 
END 
CLOSE DatabaseCursor  
DEALLOCATE DatabaseCursor
Estos scripts los puedes ejecutar cuando quieras o programarlos para que sean ejecutados periódicamente con el Agente SQL.

Puedes modificar la "SELECT" donde obtiene las bases de datos donde se realizarán las tareas para que se ejecuten sobre las bases de datos que quieras. Si lo ejecutas como está publicado aquí, se ejecutará sobre todas las bases de datos.

lunes, 14 de diciembre de 2009

Transacciones anidadas (Nested Transactions)

En ocasiones necesitamos llamar a un procedimiento dentro de una transacción, y que éste a su vez tenga otra transacción. Con lo cual tendríamos una transacción dentro de otra. Esto no es tan "sencillo" como parece...

ANTES DE NADA es inmportante saber lo que es la variable de sqlserver @@TRANCOUNT, esta variable indica el número de transacciones pendientes de finalizar. Es decir, cada vez que hacemos un BEGIN TRAN este contador aumenta en 1. Entonces, ¿qué hace un ROLLBACK TRAN?... Decrementar en 1 @@TRANCOUNT... claro.... ¡PUES NO!; la instrucción ROLLBACK establece @@TRANCOUNT a 0, es decir, deshace TODOS los cambios. Los de "su transacción" y todos los cambios hasta el primer BEGIN TRANS. En cambio COMMIT TRAN SÍ que decrementa @@TRANCOUNT en 1.

- ¿Qué pasa si usamos tal cual una transacción dentro de otra, si ignoramos lo explicado anteriormente de @@TRANCOUNT?
Pues que estaría MAL, obtendríamos un error. Por ejemplo:

-- Crea T1
   BEGIN TRAN
-- Insertar en tabla (1)
   INSERT INTO Familias(famcod, famdes, famdiasr)
   VALUES('T1', 'T1', 0)

    -- Otra Transaccion. T2
       BEGIN TRAN

    -- Insertar en tabla (2)
       INSERT INTO Familias(famcod, famdes, famdiasr)
       VALUES('T2', 'T2', 0)

    -- ROLLBACK de inserción de tabla (2) (supuestamente de T2)
       ROLLBACK TRAN

-- Hacemos commit de T1
   COMMIT TRAN
En el código anterior, si no sabemos que ROLLBACK pone @@TRANCOUNT a 0, podríamos pensar que en la tabla Familias hemos insertado el registro 'T1', 'T1', 0.
Pero lo cierto es que no, hemos obtenido un bonito error de sqlserver: La explicación es que al principio comenzamos una transacción nueva (T1), insertamos un registro, luego empezamos otra transacción (T2), insertamos otro registro, luego hacemos ROLLBACK (aquí imaginábamos que el rollback se haría sólo de T2), al hacer este rollback hemos deshecho las dos inserciones. Por eso, al hacer luego un COMMIT TRAN SqlServer nos dice: ¿estamos locos?...

Aunque se intente dar nombres a las transacciones, no conseguiremos nada, lo anterior no se puede hacer EN NINGÚN CASO.

- "Yo quiero poder hacer un ROLLBACK sin deshacer TODOS los cambios, sólo los de la segunda transacción, ¿Cómo lo hago?"
Es importante que si no has entendido todo lo anterior no continúes leyendo, vuelve a empezar en ese caso.

Aquí es donde entra en juego la instrucción "SAVE TRAN". Esta instrucción lo que hace es crear un punto de guardado (igual que cuando guardamos una partida en un video juego).
Lo que haremos será NO crear la transacción T2, sino que, llegados al punto donde deberíamos crearla, en vez de ello crearemos un punto de guardado llamado T2. De esta forma, al hacer el ROLLBACK TRAN de T2, se desharán los cambios hasta el punto de guardado, no el resto. @@TRANCOUNT no se ve afectada.

Aquí va el ejemplo de arriba, bien hecho:
-- Crea T1
   BEGIN TRAN T1
-- Insertar en tabla (1)
   INSERT INTO Familias(famcod, famdes, famdiasr)
   VALUES('T1', 'T1', 0)

    -- Guardar Transaccion
       SAVE TRAN SavePoint

    -- Insertar en tabla (2)
       INSERT INTO Familias(famcod, famdes, famdiasr)
       VALUES('T2', 'T2', 0)

    -- ROLLBACK de inserción de tabla (2)
       ROLLBACK TRAN SavePoint

-- Commit de T1
   COMMIT TRAN T1
El resultado es el esperado, si hacemos una select de la tabla familias veremos que sólo se ha insertado el registro 'T1', 'T1', 0 ya que el otro se ha deshecho.

- "Vale, muy bonito, pero quiero aplicar esto a los procedimientos almacenados que usamos en la realidad"
Imaginemos que tenemos dos procedimientos llamados P2 y P1:
CREATE PROCEDURE P2 AS BEGIN
    BEGIN TRAN T2
        INSERT INTO Familias(famcod, famdes, famdiasr)
        VALUES('T2', 'T2', 0)
    ROLLBACK TRAN T2
END
GO

CREATE PROCEDURE P1 AS BEGIN
    BEGIN TRAN T1
        INSERT INTO Familias(famcod, famdes, famdiasr)
        VALUES('T1', 'T1', 0)
        EXEC P2
    COMMIT TRAN T1
END
O sea, P1 inserta en familias, llama a P2 que inserta y hace ROLLBACK, y P1 hace COMMIT. Este es el ejemplo anterior pero separado en procedimientos almacenados.
¿Cómo lo arreglamos?, cambiamos en P2, BEGIN TRANS por SAVE TRANS... ¡PUES NO!, ¿¿por qué??, pues porque los procedimientos han de ser independientes entre si. Si ponemos SAVE TRANS y ejecutamos directamente el procedimiento P2, sqlserver nos dice: "¿estamos locos?, ¡NO HAY TRANSACCIÓN QUE GUARDAR!". Aquí vuelve a entrar en juego @@TRANCOUNT. Esta es la solución, P2 quedaría así:
ALTER PROCEDURE P2 AS BEGIN
    IF @@TRANCOUNT = 0 BEGIN TRAN T ELSE SAVE TRAN T
        INSERT INTO Familias(famcod, famdes, famdiasr)
        VALUES('T2', 'T2', 0)
    ROLLBACK TRAN T
END
De esta forma el procedimiento es independiente (caso de @@TRANCOUNT = 0) y también se puede llamar desde otro procedimiento con transacciones (caso de @@TRANCOUNT > 0).
Ahora podéis probar a ejecutar tanto P1 como P2 para comprobar que lo dicho es verdad.
¿Y si quremos que P2 NO DESHAGA los cambios?: Pues depende. Tenemos dos casos:
1 - El procedimiento se está ejecutando de forma independiente (@@TRANCOUNT = 0): En este caso hacemos COMMIT T de forma normal.
2 - El procedimiento está siendo llamado por otro, dentro de otra transacción (@@TRANCOUNT > 0): En este caso no hacemos NADA. Es, como he dicho antes, como guardar una partida en un videojuego, en este caso lo que queremos es seguir jugando sin deshacer los cambios, por tanto no cargamos la partida que hemos guardado y ya está. Si en este caso hiciésemos un COMMIT T sqlserver nos diría... "¿estamos locos?, no existe transacción T!!".

Ahora vamos a ver otro caso:
ALTER PROCEDURE P2 AS BEGIN
    SAVE TRAN T
        INSERT INTO Familias(famcod, famdes, famdiasr)
        VALUES('T2', 'T2', 0)
END
GO

ALTER PROCEDURE P1 AS BEGIN
    BEGIN TRAN T1
        INSERT INTO Familias(famcod, famdes, famdiasr)
        VALUES('T1', 'T1', 0)
        EXEC P2
    ROLLBACK TRAN T1
END
En este caso P1 inserta en familias, llama a P2 quien crea un punto de guardado e inserta en familias, y por último P1 hace un ROLLBACK TRAN T1. El resultado es que se han deshecho TODOS los cambios, no sólo hasta el punto de guardado. Si hubiésemos querido deshacer los cambios hasta el punto de guardado tendríamos que haber hecho ROLLBACK TRAN T (evidentemente en P2, porque P1 no sabe de la existencia de T).

- "Vale, pero aún no lo hemos aplicado del todo a la realidad, quiero que P1 haga commit ó rollback en función de si P2 ha realizado correctamente las operaciones o no"
Vaaaaaaale, ahí va como quedaría definitivamente como si fuese un procedimiento almacenado "real":
ALTER PROCEDURE P2 AS BEGIN
    DECLARE @ERROR AS INT
    DECLARE @TRANCOUNT AS INT SET @TRANCOUNT = @@TRANCOUNT
    IF @TRANCOUNT = 0 BEGIN TRAN T_P2 ELSE SAVE TRAN T_P2

    INSERT INTO Familias(famcod, famdes, famdiasr) VALUES('T2', 'T2', 0)
    SET @ERROR = @@ERROR IF @ERROR <> 0 GOTO HANDLE_ERROR

    IF @TRANCOUNT = 0 COMMIT TRAN T_P2
    RETURN 0

    HANDLE_ERROR:
        ROLLBACK TRAN T_P2
        RETURN @ERROR
END
GO

ALTER PROCEDURE P1 AS BEGIN
    DECLARE @ERROR AS INT

    BEGIN TRAN T_P1

    INSERT INTO Familias(famcod, famdes, famdiasr) VALUES('T1', 'T1', 0)
    SET @ERROR = @@ERROR IF @ERROR <> 0 GOTO HANDLE_ERROR

    EXEC @ERROR = P2
    IF @ERROR <> 0 GOTO HANDLE_ERROR

    COMMIT TRAN T_P1
    RETURN 0

    HANDLE_ERROR:
        ROLLBACK TRAN T_P1
        RETURN @ERROR
END
Hemos preparado P2 para que pueda funcionar de forma independiente ó siendo llamado desde otro procedimiento, dentro de otra transacción. P1 no está preparado para ello, pero en cualquier momento se puede adaptar para que así sea, de la misma forma que hemos adaptado P2.

Ahora ya podemos hacer transacciones anidadas sin ningún problema.

jueves, 12 de noviembre de 2009

Generar automáticamente procedimientos almacenados a partir de una tabla

La creación de procedimientos almacenados para un CRUD puede resultar una tarea bastante repetitiva. Aquí publico un procedimiento almacenado para SQL Server (T-SQL) que sirve para generar un script para crear 4 procedimientos almacenados a partir del nombre de una tabla.

El script creará 4 procedimientos. Todos los procedimientos que crea el script reciben como parámetros todos los campos de la tabla.
- SELECT filtrará por los campos recibidos que no sean NULL (where condicionado)
- INSERT insertará un registro con los parámetros recibidos
- UPDATE tal y como se genera no tiene sentido, simplemente borra las líneas que no quieras (del SET ó del WHERE)
- DELETE borrará registros filtrando por los campos recibidos que no sean NULL
- EXISTS devolverá registros que coincidan con el filtro creado con los campos recibidos que no sean NULL (where condicionado)

Aquí está el script:
CREATE PROCEDURE [dbo].[sp_generate]
  @tableName AS VARCHAR(100)
AS

--CAPITALIZE TABLENAME
SET @tableName = UPPER(LEFT(@tableName,1)) + RIGHT(@tableName, LEN(@tableName) -1)

--SALTO DE LÍNEA
DECLARE @nl AS CHAR
SET @nl = CHAR(10) + CHAR(13) 

--CABECERA
DECLARE @spHeaders AS VARCHAR(1000)
SET @spHeaders = 'SET ANSI_NULLS ON' + @nl +
'GO' + @nl +
'SET QUOTED_IDENTIFIER ON' + @nl +
'GO' + @nl +
'-- =============================================' + @nl +
'-- Author:  TU_NOMBRE' + @nl +
'-- Create date: ' + CONVERT(VARCHAR, GETDATE(), 3) + @nl +
'-- ============================================='

DECLARE @table AS VARCHAR(MAX)
DECLARE @column AS VARCHAR(MAX)
DECLARE @data_type AS VARCHAR(MAX)
DECLARE @length AS INT
DECLARE @precision AS INT
DECLARE @scale AS INT

--PARÁMETROS
DECLARE @spParameters AS VARCHAR(MAX) SET @spParameters = ''

--LISTA DE CAMPOS
DECLARE @fieldList AS VARCHAR(MAX) SET @fieldList = ''

--LISTA DE CAMPOS PARA EL SET DEL UPDATE
DECLARE @fieldSetList AS VARCHAR(MAX) SET @fieldSetList = ''

--LISTA DE PARÁMETROS PARA EL INSERT
DECLARE @insertParameters AS VARCHAR(MAX) SET @insertParameters = ''

--CONDICIONES
DECLARE @spConditions AS VARCHAR(MAX) SET @spConditions = ''

DECLARE c CURSOR STATIC FOR
select table_name, column_name, data_type, character_maximum_length,numeric_precision, numeric_scale from information_schema.columns where table_name = @tableName order by ordinal_position
OPEN c FETCH NEXT FROM c INTO @table, @column, @data_type, @length, @precision, @scale
WHILE @@FETCH_STATUS = 0 BEGIN

 SET @spParameters = @spParameters + (CASE WHEN LEN(@spParameters) >0 THEN @nl + ' ,' ELSE '  ' END) + '@' + @column + ' ' + UPPER(@data_type) + (CASE @data_type WHEN 'VARCHAR' THEN '('+CAST(@length AS VARCHAR)+')' WHEN 'DECIMAL' THEN '('+CAST(@precision AS VARCHAR)+', '+CAST(@scale AS VARCHAR)+')' ELSE '' END) + ' = NULL'
 SET @fieldList = @fieldList + (CASE WHEN LEN(@fieldList) >0 THEN @nl + '    ,' ELSE '' END) + @column
 SET @spConditions = @spConditions + (CASE WHEN LEN(@spConditions) >0 THEN @nl + '   AND ' ELSE '' END) + '(@' + @column + ' IS NULL OR @' + @column + '=' + @column + ')'
 SET @fieldSetList = @fieldSetList + (CASE WHEN LEN(@fieldSetList) >0 THEN @nl + '     ,' ELSE '      ' END) + @column + ' = @' + @column
 SET @insertParameters = @insertParameters + (CASE WHEN LEN(@insertParameters) >0 THEN @nl + '    ,' ELSE '' END) + '@' + @column

 FETCH NEXT FROM c INTO @table, @column, @data_type, @length, @precision, @scale
END
CLOSE c DEALLOCATE c

--********************************
--*********** SELECT *************
--********************************
DECLARE @SELECT AS VARCHAR(MAX)
SET @SELECT = @spHeaders + @nl
SET @SELECT = @SELECT + 'CREATE PROCEDURE ' + @tableName + '_Select' + @nl
SET @SELECT = @SELECT + @spParameters + @nl
SET @SELECT = @SELECT + 'AS' + @nl + ' SET NOCOUNT OFF;' + @nl + @nl
SET @SELECT = @SELECT + '    SELECT ' + @fieldList + @nl
SET @SELECT = @SELECT + '    FROM ' + @table + @nl
SET @SELECT = @SELECt + '    WHERE ' + @spConditions + @nl

--********************************
--*********** UPDATE *************
--********************************
DECLARE @UPDATE AS VARCHAR(MAX)
SET @UPDATE = @spHeaders + @nl
SET @UPDATE = @UPDATE + 'CREATE PROCEDURE ' + @tableName + '_Update' + @nl
SET @UPDATE = @UPDATE + @spParameters + @nl
SET @UPDATE = @UPDATE + 'AS' + @nl + ' SET NOCOUNT OFF;' + @nl + @nl
SET @UPDATE = @UPDATE + '    UPDATE ' + @table + ' SET ' + @nl
SET @UPDATE = @UPDATE + @fieldSetList + @nl
SET @UPDATE = @UPDATE + '    WHERE ' + @spConditions + @nl

--********************************
--*********** DELETE *************
--********************************
DECLARE @DELETE AS VARCHAR(MAX)
SET @DELETE = @spHeaders + @nl
SET @DELETE = @DELETE + 'CREATE PROCEDURE ' + @tableName + '_Delete' + @nl
SET @DELETE = @DELETE + @spParameters + @nl
SET @DELETE = @DELETE + 'AS' + @nl + ' SET NOCOUNT OFF;' + @nl + @nl
SET @DELETE = @DELETE + '    DELETE FROM ' + @table + @nl
SET @DELETE = @DELETE + '    WHERE ' + @spConditions + @nl

--********************************
--*********** INSERT *************
--********************************
DECLARE @INSERT AS VARCHAR(MAX)
SET @INSERT = @spHeaders + @nl
SET @INSERT = @INSERT + 'CREATE PROCEDURE ' + @tableName + '_Insert' + @nl
SET @INSERT = @INSERT + @spParameters + @nl
SET @INSERT = @INSERT + 'AS' + @nl + ' SET NOCOUNT OFF;' + @nl + @nl
SET @INSERT = @INSERT + '    INSERT INTO ' + @table + '(' + @nl
SET @INSERT = @INSERT + '     ' + @fieldList + @nl
SET @INSERT = @INSERT + ' )' + @nl + ' VALUES(' + @nl + '     ' + @insertParameters + @nl
SET @INSERT = @INSERT + ' )' + @nl

--********************************
--*********** EXISTS *************
--********************************
DECLARE @EXISTS AS VARCHAR(MAX)
SET @EXISTS = @spHeaders + @nl
SET @EXISTS = @EXISTS + 'CREATE PROCEDURE ' + @tableName + '_Exists' + @nl
SET @EXISTS = @EXISTS + @spParameters + @nl
SET @EXISTS = @EXISTS + ' ,@exists BIT OUT' + @nl
SET @EXISTS = @EXISTS + 'AS' + @nl + ' SET NOCOUNT OFF;' + @nl + @nl
SET @EXISTS = @EXISTS + '    IF EXISTS (' + @nl + ' SELECT ' + LEFT(@fieldList,CHARINDEX(@nl,@fieldList))
SET @EXISTS = @EXISTS + '    FROM ' + @table + @nl
SET @EXISTS = @EXISTS + '    WHERE ' + @spConditions + @nl + ' )' + @nl
SET @EXISTS = @EXISTS + ' SET @exists = 1' + @nl + ' ELSE SET @exists = 0'

--MOSTRAR GENERADOS
PRINT + '-- =====INSERT==================================' + @nl + @INSERT
PRINT + '-- =====DELETE==================================' + @nl + @DELETE
PRINT + '-- =====UPDATE==================================' + @nl + @UPDATE
PRINT + '-- =====SELECT==================================' + @nl + @SELECT
PRINT + '-- =====EXISTS==================================' + @nl + @EXISTS

Se usa así:
sp_generate NOMBRE_DE_TABLA