SQLShack (Español)

Intro

En las bases de datos relacionales, las operaciones se realizan en un conjunto de filas. Por ejemplo, una instrucción SELECT devuelve un conjunto de filas que se denomina conjunto de resultados. A veces, la lógica de la aplicación necesita trabajar con una fila a la vez en lugar de todo el conjunto de resultados a la vez. En T-SQL, una forma de hacer esto es usando un CURSOR.

Si posee habilidades de programación, probablemente usaría un ciclo como FOR o WHILE para iterar a través de un elemento a la vez, haga algo con los datos y el trabajo está hecho. En T-SQL, un CURSOR es un enfoque similar y podría ser el preferido porque sigue la misma lógica. Pero tenga cuidado, tome este camino y pueden surgir problemas.

Hay algunos casos en los que el uso de CURSOR no causa tanto lío, pero generalmente deben evitarse. A continuación, mostraremos algunos ejemplos en los que el uso de un CURSOR crea problemas de rendimiento y veremos que el mismo trabajo se puede realizar de muchas otras formas.

Para el propósito de esta demostración usaremos la base de datos AdventureWorks2012, y digamos que queremos obtener algunos datos de. table, para cada producto que requiera menos de un día para fabricarse, es decir, de table ..

Un ejemplo de cursor

Comencemos usando un CURSOR y escribamos la siguiente sintaxis:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

USE AdventureWorks2012
GO
DECLARE @id int
DECLARE cursorT CURSOR
–LOCAL STATIC
–LOCAL FAST_FORWARD
–LOCAL READ_ONLY FORWARD_ ONLY
PARA
SELECT ProductId
FROM AdventureWorks2012.Production.Product
WHERE DaysToManufacture < = 1
ABRIR cursorT
FETCH NEXT FROM cursorT INTO @id
MIENTRAS @@ FETCH_STATUS = 0
BEGIN
SELECT * FROM Production.ProductInventory
WHERE ProductID = @ id
BUSCAR SIGUIENTE DESDE cursorT INTO @id
END
CERRAR cursorT
DEALLOCATE cursorT

Después de una breve pausa para el café , la consulta terminó de ejecutarse y devolvió 833 filas en el tiempo que se muestra a continuación. Es importante mencionar que las sintaxis elegidas arriba son solo para propósitos de demostración, y no hice ningún ajuste de índice para acelerar las cosas.

En la ejecución del cursor, tenemos dos pasos. Paso uno, el posicionamiento, cuando el cursor establece su posición en una fila del conjunto de resultados. Paso dos, la recuperación, cuando obtiene los datos de esa fila específica en una operación llamada FETCH.

En nuestro ejemplo, el cursor establece su posición en la primera fila devuelta por el primer SELECT y obtiene el valor ProductID que coincide con la condición WHERE en la variable @id. Luego, el segundo SELECT usa el valor de la variable para obtener datos. y se recupera la siguiente fila. Estas operaciones se repiten hasta que no hay más filas con las que trabajar.

Finalmente, la sintaxis CLOSE libera el conjunto de resultados actual y elimina los bloqueos de las filas utilizadas por el cursor, y DEALLOCATE elimina la referencia del cursor.

Nuestras tablas de demostración son relativamente pequeñas y contienen aproximadamente 1,000 y 500 filas. Si tuviéramos que recorrer tablas con millones de filas, duraría una cantidad considerable de tiempo y los resultados no nos agradarían.

Démosle a nuestro cursor otra oportunidad y descomentemos la línea –LOCAL STATIC. Hay muchos argumentos que podemos usar en una definición de cursor, más sobre eso en este enlace CURSOR Argumentos, pero por ahora centrémonos en lo que significan estas dos palabras.

Cuando especificamos la palabra clave LOCAL, el alcance del cursor es local para el lote en el que fue creado y es válido solo en este alcance. Una vez que el lote termina de ejecutarse, el cursor se desasigna automáticamente. Además, el cursor puede ser referenciado por un procedimiento almacenado, disparador o por una variable de cursor local en un lote.

La palabra clave STATIC hace una copia temporal de los datos usados por el cursor en tempdb en una tabla temporal . Aquí tenemos algunas trampas. Un cursor ESTÁTICO es de solo lectura y también se conoce como cursor de instantánea porque solo funciona con los datos desde el momento en que se abrió, lo que significa que no mostrará ningún cambio realizado en la base de datos en el conjunto de datos utilizados por el cursor.Básicamente, ninguna actualización, eliminación o inserción realizada después de que el cursor estuvo abierto será visible en el conjunto de resultados de los cursores a menos que cierre y vuelva a abrir el cursor.

Tenga esto en cuenta antes de utilizar estos argumentos y compruebe si se ajusta a sus necesidades. Pero veamos si nuestro cursor es más rápido. A continuación tenemos los resultados:

12 segundos son mucho mejores que 4 minutos y 47 segundos, pero tenga en cuenta que restricciones explicadas anteriormente.

Si ejecutamos la sintaxis usando este argumento LOCAL READ_ONLY FORWARD_ONLY obtenemos los mismos resultados. READ_ONLY FORWARD_ONLY el cursor se puede desplazar solo desde la primera fila hasta la última. Si falta la palabra clave STATIC, el cursor es dinámico y usa un plan dinámico si está disponible. Pero hay casos en los que un plan dinámico es peor que uno estático.

¿Qué sucede cuando descomentamos –LOCAL FAST_FORWARD?

FAST_FORWARD es equivalente al cursor READ_ONLY y FORWARD_ONLY pero tiene la capacidad de elegir el mejor plan entre uno estático o dinámico.

LOCAL FAST_FORWARD parece ser la mejor opción debido a su flexibilidad para elija entre un plan estático o dinámico, pero FORWARD_ONLY también hace muy bien el trabajo. Aunque obtuvimos el resultado en un promedio de 12 segundos usando ambos métodos, estos argumentos deben probarse adecuadamente antes de elegir uno de ellos.

Hay muchas formas de obtener el mismo resultado, mucho más rápido y con menos impacto en el rendimiento.

Alternativa de cursor

Un método es usar JOIN, y como podemos ver a continuación, los resultados son considerablemente mejores.

Primero, tenemos que habilitar el tiempo de estadísticas para medir el tiempo de ejecución de SQL Server y hacer un INNER JOIN entre dos tablas,. y . en la columna ProductID. Después de presionar el botón EJECUTAR, obtuvimos los mismos resultados en 330 ms, en comparación con las 04:47 del método del cursor, y tenemos una sonrisa en la cara.

1
2
3
4
5
6
7
8
9

USE AdventureWorks2012
GO
ESTABLECER HORA DE ESTADÍSTICAS EN
SELECCIONAR * FROM Production.ProductInventory como pinv
INNER JOIN Producción.Producto como pp
ON pinv.ProductID = pp.ProductID
DONDE pp.DaysToManufacture < = 1

No necesitamos iterar a través de cada fila para obtener lo que necesitamos, no tenemos más bucles, no hay cláusula while, no iter En cambio, estamos trabajando con conjuntos de datos, obteniendo lo que queremos más rápido y escribiendo menos código.

Un uso apropiado del cursor

Ahora que hemos visto cuánto daño cursor puede hacer, veamos un ejemplo donde podemos hacer uso de él.

Supongamos que queremos seleccionar el tamaño y el número de filas solo para ciertas tablas de una base de datos. Para lograr esto, obtendremos todos los nombres de tabla basados en criterios de information_schema.tables y usando un CURSOR recorreremos cada uno de los nombres de esa tabla y ejecutaremos el procedimiento almacenado sp_spaceused pasando un nombre de tabla a la vez para obtener la información que necesitamos .

Usaremos la misma base de datos AdventureWorks2012 y obtendremos todas las tablas del esquema de Ventas que contenga el nombre ‘Ventas’. Por cada nombre de tabla devuelto, queremos ver toda la información de information_schema.tables.

A continuación tenemos la sintaxis de T-SQL y los resultados obtenidos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

USE AdventureWorks2012
GO
DECLARE @TableName VARCHAR (50) – nombre de la tabla del esquema «Sales»
DECLARE @Param VARCHAR (50) – parámetro para el procedimiento «sp_spaceused»
DECLARAR db_cursor CURSOR PARA
: seleccione solo la pestaña «Ventas» es
SELECT TABLE_NAME FROM information_schema.tables
DONDE TABLE_NAME como «% Sales%» y TABLE_TYPE = «BASE TABLE»
ABRIR db_cursor
BUSCAR SIGUIENTE DESDE db_cursor EN @TableName
MIENTRAS @@ FETCH_STATUS = 0
BEGIN
–concatenar cada nombre de tabla en una variable y pasarlo al procedimiento almacenado
SET @ Param = «Sales.»+ @ TableName
– ejecutar el procedimiento almacenado para cada nombre de tabla a la vez
EXEC sp_spaceused @Param
BUSCAR SIGUIENTE DE db_cursor EN @TableName – obtiene el nombre de la siguiente tabla
END
CLOSE db_cursor
DEALLOCATE db_cursor

Este es un método en el que CURSOR es útil al iterar algunos datos una fila a la vez y obtiene el resultado necesario. En este caso particular, el cursor hace el trabajo sin tener implicaciones en el rendimiento y es fácil de usar.

Conclusiones

Ahí lo tenemos. Mostramos algunos ejemplos con lo bueno, lo malo y lo feo al usar cursores. En la mayoría de los casos, podemos usar JOINS, incluso cláusulas WHILE, SSIS paquetes u otros métodos alternativos para obtener el mismo resultado más rápido, con menos impacto en el rendimiento e incluso escribiendo menos líneas de sintaxis.

Cuando están tratando con un entorno OLTP y grandes conjuntos de datos para procesar, la palabra ‘CURSOR’ no debe usarse.

En SQL, es una buena práctica pensar en realizar operaciones en conjuntos de datos, en lugar de pensar de manera programática, utilizando iteraciones o bucles, porque este tipo de enfoque no es recomendado ni destinado para este uso. Intentar utilizar bucles como FOR o FOREACH de lenguajes de programación y asociar esa lógica con operaciones SQL, es un obstáculo para conseguir la solución adecuada a nuestras necesidades. Tenemos que pensar en operaciones basadas en conjuntos en lugar de una fila a la vez para obtener los datos que necesitamos.

Los cursores podrían usarse en algunas aplicaciones para operaciones serializadas como se muestra en el ejemplo anterior, pero generalmente deberían evitarse porque tienen un impacto negativo en el rendimiento, especialmente cuando se opera con un gran conjunto de datos.

  • Autor
  • Publicaciones recientes
Soy un DBA en Yazaki Component Technology con 3 años de experiencia en SQL Server.
Tengo una licenciatura en ingeniería informática y también poseo habilidades en .NET, C # y Windows Server. Desarrollé experiencia en ajuste y monitoreo de desempeño, desarrollo de T-SQL, estrategia de respaldo, administración y configuración de bases de datos.
Me apasionan las cosas de TI, los videojuegos, las buenas películas y la música electro-house me ayuda a trabajar cuando la oficina está ruidoso.
Ver todas las publicaciones de Sergiu Onet

Últimas publicaciones de Sergiu Onet (ver todas)
  • Uso de cursores de SQL Server – Ventajas y desventajas – 23 de marzo de 2016

Write a Comment

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