A Lauren
El titulo de este post es un pequeño homenaje a una novela del Nobel Gabriel García Márquez, su nombre me vino a la mente después de asociarlo a una serie de usos y malas prácticas que por lo general los desarrolladores cometemos al iniciar con la programación a nivel de Base de Datos; queriendo de forma inconsciente extrapolar todos nuestros conocimientos y “manias”en lenguajes procedimentales y orientados a objetos para transferirlos a nuestras soluciones en T-SQL.
Cuantas veces nos hemos topado en diferentes foros, reuniones o en la misma oficina con comentarios como: Eso es fácil, mételo en un cursor y ….. La triste historia inicia, en especial cuando meses después escuchamos: La consulta está demasiado lenta, se tarda N horas y sigue creciendo… Generalmente comienzan a llover soluciones mágicas, en las que por descabelladas que puedan parecer se han llegado a implementar, algunas simples y poco efectivas hasta algunas muy caras y poco productivas.
Es en este punto donde elementos como los Cursores transtornan la salud de nuestras aplicaciones, convirtiéndose en un lastre difícil de quitar, en especial si forma parte de la cultura del desarrollador. Como principio fundamental que puede resultar orientador esta una afirmación que hacía en uno de sus textos Itzik Ben-Gan al mencionar que el uso de cursores representaba una opción viable si se requería manipular una fila a la vez.
A continuación tomo prestado de los foros de MSDN una consulta en la que el usuario se avocaba directamente al cursor como el método de solución elegido, decía algo así:
“Quiero preguntarles como puedo hacer un script en el que se maneje un cursor para depurar una base de datos...bueno tengo una tabla llamada auditoria_homologacion donde tengo varios registros con id autonumerico pero el resto de los campos están repetidos, entonces cuando se busca un registro por cualquier campo(menos el id_auditoria) me aparecen varios registros iguales, entonces debo buscar los registros repetidos, luego tengo que mirar que esos registros que están repetidos no estén amarrados a otra tabla que se llama log_error_homologacion en donde tengo el fk_id_auditoria de la tabla auditoria_homologacion, (entonces los que están en la tabla log_error_homologacion no se borran), pero hay mas registros repetidos los cuales no tienen error pero debo borrar los demás que están repetidos (por ejemplo tengo 5 registros con diferente id_auditoria pero el resto de los campos en igual..debo borrar 4 y dejar 1) y no se como hacerlo por medio de un cursor, agradezco de antemano la colaboración me es de gran ayuda gracias.”
Aunque la forma de explicarlo no fue la idónea, la idea central consistía en obtener un conjunto de filas duplicadas y verificar que las mismas se encontraban en una tabla de log, caso contrario eliminarlas. Partiendo de la afirmación de Ben-Gan en este caso no es necesario procesar una fila a la vez, por el contrario generar una solución basada en cursores sería extremadamente deficiente y muy tediosa.
Para solventar esta situación procedimos al uso de una solución basada en CTE (Common Table Expression) y la función ROW_NUMBER(), aquí presento el código:
DECLARE @AuditoriaHomologacion TABLE (IDAuditoria INT,Columna1 VARCHAR(30),Columna2 INT,Columna3 VARCHAR(30),Columna4 INT)
DECLARE @LogErrorHomologacion TABLE (IDAuditoria INT)
INSERT @AuditoriaHomologacion
VALUES (1,'LAUREN FONSECA',1,'NICARAGUA',2),
(2,'LAUREN FONSECA',1,'NICARAGUA',2),
(3,'LAUREN FONSECA',1,'NICARAGUA',2),
(4,'OCTAVIO HERNANDEZ',1,'ESPANA',2),
(5,'OCTAVIO HERNANDEZ',1,'ESPANA',2),
(6,'LAUREN GIOVANNA HERNANDEZ',1,'ESPANA',2),
(7,'LAUREN GIOVANNA HERNANDEZ',1,'ESPANA',2)
INSERT @LogErrorHomologacion
VALUES (7),(5)
; WITH MiCTE AS
(
SELECT IDAuditoria,ROW_NUMBER() OVER(PARTITION BY Columna1,Columna2,Columna3,Columna4 ORDER BY IDAuditoria)AS 'SEC'
FROM @AuditoriaHomologacion
)
DELETE FROM MiCTE
WHERE MiCTE.SEC >1 AND NOT EXISTS(SELECT IDAuditoria FROM @LogErrorHomologacion L WHERE L.IDAuditoria=MiCTE.IDAuditoria)
--Verificando filas eliminadas
SELECT * FROM @AuditoriaHomologacion
En esta solución planteada se potenciaban aspectos que permiten contar con un código mantenible y sobre todo enfocado en aspectos orientados al manejo de <<set theory>>.
Pero como no todo es desolador y gris, un escenario común en donde el uso de cursores es viable puede ser en tareas administrativas de nuestras base de datos, el siguiente ejemplo permite recorrer todas las tablas de una Base de Datos y obtener información sobre las mismas (filas, tamaño,etc) a través de la ejecución del sp_spacedused.
USE AdventureWorks
GO
DECLARE @tabla VARCHAR(128)
CREATE TABLE #rowcount (tablename varchar(128), rowcnt int)
exec sp_MSforeachtable
'insert into #rowcount select ''?'', count(*) from ?'
DECLARE tablas CURSOR FOR
SELECT tablename FROM #rowcount
OPEN tablas
FETCH NEXT FROM tablas INTO @tabla
WHILE @@fetch_status = 0
BEGIN
EXEC sp_spaceused @tabla
END
CLOSE tablas
DEALLOCATE tablas
DROP TABLE #rowcount
Por lo pronto un hasta luego, seguimos escribiendo, pero sobre todo aprendiendo.
Email: info@geohernandez.com Website: http://geeks.ms/blogs/ghernandez