Del Cursor y otros demonios

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 “manías”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

  FETCH NEXT FROM tablas INTO @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

A Lauren …

 

 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *