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
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
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:
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]
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.
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):
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.
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 transaccionesDECLARE @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 índicesSET 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:
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:
- "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:
¿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í:
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:
- "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":
Ahora ya podemos hacer transacciones anidadas sin ningún problema.
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:
Se usa así:
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
Suscribirse a:
Entradas (Atom)
