sql >> Base de Datos >  >> RDS >> Database

Reemplazo de cursores SQL con alternativas para evitar problemas de rendimiento

En este artículo, veremos algunas alternativas al uso de cursores SQL que pueden ayudar a evitar problemas de rendimiento causados ​​por el uso de cursores.

Antes de discutir las alternativas, repasemos el concepto general de los cursores SQL.

Descripción general rápida de los cursores SQL

Los cursores de SQL se utilizan principalmente cuando las operaciones basadas en conjuntos no son aplicables y se requiere acceder a los datos y realizar operaciones fila por fila en lugar de aplicar una sola operación basada en conjuntos a un objeto completo (como una tabla o un conjunto de tablas).

Definición sencilla

Un cursor SQL brinda acceso a los datos de una fila a la vez, lo que le brinda un control directo fila por fila sobre el conjunto de resultados.

Definición de Microsoft

De acuerdo con la documentación de Microsoft, las declaraciones de Microsoft SQL Server producen un conjunto de resultados completo, pero hay ocasiones en las que es mejor procesarlas una fila a la vez, lo que se puede hacer abriendo un cursor en el conjunto de resultados.

El proceso de 5 pasos para usar un cursor

El proceso de usar un cursor SQL se puede describir generalmente de la siguiente manera:

  1. Declarar Cursor
  2. Cursor abierto
  3. Obtener filas
  4. Cerrar cursor
  5. Desasignar cursor

Nota importante

Tenga en cuenta que, según Vaidehi Pandere, los cursores son punteros que ocupan la memoria del sistema, que de otro modo estaría reservada para otros procesos importantes. Es por eso que recorrer un gran conjunto de resultados usando cursores no suele ser la mejor idea, a menos que haya una razón legítima para hacerlo.

Para obtener información más detallada sobre esto, no dude en consultar mi artículo Cómo utilizar cursores SQL para fines especiales.

Ejemplo de cursor SQL

Primero, veremos un ejemplo de cómo se puede usar un cursor SQL para cambiar el nombre de los objetos de la base de datos uno por uno.

Para crear un cursor SQL que necesitamos, configuremos una base de datos de muestra para que podamos ejecutar nuestros scripts contra ella.

Configurar base de datos de muestra (UniversityV3)

Ejecute el siguiente script para crear y completar la base de datos de muestra UniversityV3 con dos tablas:

-- (1) Create UniversityV3 sample database

CREATE DATABASE UniversityV3;

GO

USE UniversityV3

-- (2) Create Course table

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Course') 

DROP TABLE dbo.Course 

CREATE TABLE [dbo].[Course] (

    [CourseId] INT           IDENTITY (1, 1) NOT NULL,

    [Name]     VARCHAR (30)  NOT NULL,

    [Detail]   VARCHAR (200) NULL,

    CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED ([CourseId] ASC)

);

-- (3) Create Student table

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Student') 

DROP TABLE dbo.Student 

CREATE TABLE [dbo].[Student] (

    [StudentId] INT           IDENTITY (1, 1) NOT NULL,

    [Name]      VARCHAR (30)  NULL,

    [Course]    VARCHAR (30)  NULL,

    [Marks]     INT           NULL,

    [ExamDate]  DATETIME2 (7) NULL,

    CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED ([StudentId] ASC)

);

-- (4) Populate Course table

SET IDENTITY_INSERT [dbo].[Course] ON

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (1, N'DevOps for Databases', N'This is about DevOps for Databases')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (2, N'Power BI Fundamentals', N'This is about Power BI Fundamentals')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (3, N'T-SQL Programming', N'About T-SQL Programming')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (4, N'Tabular Data Modeling', N'This is about Tabular Data Modeling')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (5, N'Analysis Services Fundamentals', N'This is about Analysis Services Fundamentals')

SET IDENTITY_INSERT [dbo].[Course] OFF



-- (5) Populate Student table

SET IDENTITY_INSERT [dbo].[Student] ON

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (1, N'Asif', N'Database Management System', 80, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (2, N'Peter', N'Database Management System', 85, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (3, N'Sam', N'Database Management System', 85, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (4, N'Adil', N'Database Management System', 85, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (5, N'Naveed', N'Database Management System', 90, N'2016-01-01 00:00:00')

SET IDENTITY_INSERT [dbo].[Student] OFF

Crear un cursor SQL para renombrar tablas (_Backup)

Ahora considere cumplir con la siguiente especificación usando un cursor:

  1. Necesitamos agregar '_Backup' a los nombres de todas las tablas existentes en una base de datos
  2. Las tablas que ya tienen '_Backup' en su nombre no deben cambiarse de nombre

Vamos a crear un cursor SQL para cambiar el nombre de todas las tablas en la base de datos de muestra agregando '_Backup' al nombre de cada tabla y asegurándonos de que las tablas que contienen '_Backup' en su nombre no se renombrarán nuevamente ejecutando el siguiente código:

-- Declaring the Student cursor to rename all tables by adding ‘_backup’ to their names and also making sure that all tables that are already named correctly will be skipped:

USE UniversityV3
GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

DECLARE Student_Cursor CURSOR FOR SELECT T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T;

OPEN Student_Cursor

FETCH NEXT FROM Student_Cursor INTO @TableName

WHILE @@FETCH_STATUS = 0

BEGIN

IF RIGHT(@TableName,6)<>'Backup' -- If Backup table does not exist then rename the table

BEGIN

SET @[email protected]+'_Backup' -- Add _Backup to the table’s current name

EXEC sp_rename @TableName,@NewTableName -- Rename table as OLD table

END

ELSE

PRINT 'Backup table name already exists: '[email protected]

FETCH NEXT FROM Student_Cursor -- Get next row data into cursor and store it in variables

INTO @TableName

END

CLOSE Student_Cursor -- Close cursor locks on the rows

DEALLOCATE Student_Cursor -- Release cursor reference

Ejecute el script de cambio de nombre y vea los resultados

Ahora, presione F5 en SSMS (SQL Server Management Studio) para ejecutar el script y ver los resultados:

Actualizar los nombres de las tablas en el explorador de objetos de SSMS muestra claramente que los hemos cambiado con éxito según lo especificado.

Ejecutemos el script nuevamente presionando F5 nuevamente y veamos los resultados:

Creación de un cursor SQL para restablecer _Backup Naming

También necesitamos crear una secuencia de comandos que use un cursor SQL para revertir los nombres de las tablas que acabamos de cambiar a los iniciales; lo haremos eliminando '_Backup' de sus nombres.

El siguiente script nos permitirá hacer precisamente eso:

-- Declare the Student cursor to reset tables names _backup to their original forms by removing ‘_backup’

USE UniversityV3

GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

DECLARE Student_Cursor CURSOR FOR SELECT T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T;

OPEN Student_Cursor

FETCH NEXT FROM Student_Cursor INTO @TableName

WHILE @@FETCH_STATUS = 0

BEGIN

IF RIGHT(@TableName,6)='Backup' -- If Backup table name exists then reset (rename) it

BEGIN

SET @NewTableName=SUBSTRING(@TableName,1,LEN(@TableName)-7) -- Remove _Backup from the table name

EXEC sp_rename @TableName,@NewTableName -- Rename table 

END

ELSE

PRINT 'Backup table name already reset: '[email protected]

FETCH NEXT FROM Student_Cursor – Get the data of the next row into cursor and store it in variables

INTO @TableName

END

CLOSE Student_Cursor -- Close cursor locks on the rows

DEALLOCATE Student_Cursor -- Release cursor reference

Ejecute el script de restablecimiento y vea los resultados

La ejecución del script muestra que los nombres de las tablas se han restablecido correctamente:

Estos fueron los ejemplos de algunos escenarios en los que es difícil evitar el uso de cursores SQL debido a la naturaleza del requisito. Sin embargo, todavía es posible encontrar un enfoque alternativo.

Alternativas de cursores SQL

Hay dos alternativas más comunes para los cursores SQL, así que veamos cada una de ellas en detalle.

Alternativa 1:Tabla de Variables

Una de estas alternativas son las variables de tabla.

Las variables de tabla, al igual que las tablas, pueden almacenar múltiples resultados, pero con algunas limitaciones. Según la documentación de Microsoft, una variable de tabla es un tipo de datos especial que se utiliza para almacenar un conjunto de resultados para su procesamiento posterior.

Sin embargo, tenga en cuenta que las variables de tabla se utilizan mejor con conjuntos de datos pequeños.

Las variables de tabla pueden ser muy eficientes para consultas a pequeña escala, ya que funcionan como variables locales y se limpian automáticamente al salir del alcance.

Estrategia de variables de tabla:

Usaremos variables de tabla en lugar de cursores SQL para cambiar el nombre de todas las tablas de una base de datos siguiendo estos pasos:

  1. Declarar una variable de tabla
  2. Almacene los nombres e ID de las tablas en la variable de tabla que declaramos
  3. Establezca el contador en 1 y obtenga el número total de registros de la variable de la tabla
  4. Utilice un bucle 'while' siempre que el contador sea menor o igual que el número total de registros
  5. Dentro del bucle 'while', cambiaremos el nombre de las tablas una por una siempre que no hayan cambiado de nombre y aumentemos el contador para cada tabla

Código de variable de tabla:

Ejecute el siguiente script SQL que crea y usa una variable de tabla para cambiar el nombre de las tablas:

-- Declare Student Table Variable to rename all tables by adding ‘_backup’ t their name and also making sure that already renamed tables are skipped

USE UniversityV3

GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

DECLARE @StudentTableVar TABLE -- Declaring a table variable to store tables names
(
TableId INT,

TableName VARCHAR(40))

INSERT INTO @StudentTableVar -- insert tables names into the table variable 

SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T

DECLARE @TotalRows INT=(SELECT COUNT(*) FROM @StudentTableVar),@i INT=1 -- Get total rows and set counter to 1

WHILE @i<[email protected] -- begin as long as i (counter) is less than or equal to the total number of records

BEGIN -- ‘While’ loop begins here

SELECT @TableName=TableName from @StudentTableVar WHERE [email protected]

IF RIGHT(@TableName,6)<>'Backup' -- If a Backup table does not exist, then rename the table

BEGIN

SET @[email protected]+'_Backup' -- Add _Backup to the table’s current name

EXEC sp_rename @TableName,@NewTableName -- Rename the table as OLD table

END

ELSE

PRINT 'Backup table name already exists: '[email protected]

SET @[email protected]+1

END -- 'While' loop ends here

Ejecutar el script y ver los resultados

Ahora, ejecutemos el script y verifiquemos los resultados:

Alternativa 2:Tablas Temporales

También podemos usar tablas temporales en lugar de cursores SQL para iterar el conjunto de resultados una fila a la vez.

Las tablas temporales se han utilizado durante mucho tiempo y proporcionan una forma excelente de reemplazar los cursores para grandes conjuntos de datos.

Al igual que las variables de tabla, las tablas temporales pueden contener el conjunto de resultados para que podamos realizar las operaciones necesarias al procesarlo con un algoritmo de iteración como un bucle 'while'.

Estrategia de mesa temporal:

Usaremos una tabla temporal para cambiar el nombre de todas las tablas en la base de datos de muestra siguiendo estos pasos:

  1. Declarar una tabla temporal
  2. Almacene los nombres de las tablas y las identificaciones en la tabla temporal que acabamos de declarar
  3. Establezca el contador en 1 y obtenga el número total de registros de la tabla temporal
  4. Utilice un bucle 'while' siempre que el contador sea menor o igual que el número total de registros
  5. Dentro del bucle 'while', cambie el nombre de las tablas una por una siempre que no hayan cambiado de nombre y aumente el contador para cada tabla

Restablecer las tablas

Necesitamos restablecer los nombres de las tablas a su forma inicial eliminando '_Backup' del final de sus nombres, así que vuelva a ejecutar el script de restablecimiento que ya escribimos y usamos anteriormente para que podamos aplicar otro método para cambiar el nombre de las tablas.

Código de tabla temporal:

Ejecute el siguiente script SQL para crear y usar una tabla temporal para cambiar el nombre de todas las tablas en nuestra base de datos:

-- Declare the Student Temporary Table to rename all tables by adding ‘_backup’ to their names while also making sure that already renamed tables are skipped

USE UniversityV3

GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

CREATE TABLE #Student -- Declaring a temporary table

(
TableId INT,
TableName VARCHAR(40)
)

INSERT INTO #Student -- insert tables names into the temporary table

SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T

DECLARE @TotalRows INT=(SELECT COUNT(*) FROM #Student),@i INT=1 -- Get the total amount of rows and set the counter to 1

WHILE @i<[email protected] -- begin as long as i (counter) is less than or equal to the total number of records

BEGIN -- ‘While’ loop begins here

SELECT @TableName=TableName from #Student WHERE [email protected]

IF RIGHT(@TableName,6)<>'Backup' -- If a Backup table does not exist, then rename the table

BEGIN

SET @[email protected]+'_Backup' -- Add ‘_Backup’ to the table’s current name

EXEC sp_rename @TableName,@NewTableName -- Rename the table as OLD table

END

ELSE

PRINT 'Backup table name already exists: '[email protected]

SET @[email protected]+1

END -- While loop ends here

DROP TABLE #Student

Ejecute el script y compruebe el resultado

Ahora, ejecutemos el script para ver los resultados:

Cosas que hacer

Ahora que está familiarizado con las alternativas a los cursores SQL, como el uso de variables de tabla y tablas temporales, intente hacer lo siguiente para familiarizarse con la aplicación de este conocimiento en la práctica:

  1. Cree y cambie el nombre de los índices de todas las tablas en una base de datos de muestra, primero a través de un cursor y luego usando métodos alternativos (variables de tabla y tablas temporales)
  2. Revertir los nombres de las tablas de este artículo a sus nombres iniciales utilizando métodos alternativos (tablas temporales y variables de tabla)
  3. También puede consultar los primeros ejemplos en mi artículo Cómo usar cursores SQL para propósitos especiales e intentar completar tablas con muchas filas y medir las estadísticas y el tiempo de las consultas para comparar el método básico del cursor con las alternativas.