Database
 sql >> Base de Dados >  >> RDS >> Database

Substituindo cursores SQL por alternativas para evitar problemas de desempenho


Neste artigo, veremos algumas alternativas ao uso de cursores SQL que podem ajudar a evitar problemas de desempenho causados ​​pelo uso de cursores.

Antes de discutir as alternativas, vamos rever o conceito geral de cursores SQL.

Visão geral rápida dos cursores SQL


Os cursores SQL são usados ​​principalmente onde as operações baseadas em conjunto não são aplicáveis ​​e você precisa acessar dados e executar operações uma linha por vez, em vez de aplicar uma única operação baseada em conjunto a um objeto inteiro (como uma tabela ou um conjunto de tabelas).


Definição simples


Um cursor SQL fornece acesso aos dados uma linha de cada vez, fornecendo assim controle direto linha por linha sobre o conjunto de resultados.

Definição da Microsoft


De acordo com a documentação da Microsoft, as instruções do Microsoft SQL Server produzem um conjunto de resultados completo, mas há momentos em que é melhor processá-lo uma linha por vez – o que pode ser feito abrindo um cursor no conjunto de resultados.

O processo de 5 etapas para usar um cursor


O processo de usar um cursor SQL pode ser geralmente descrito da seguinte forma:
  1. Declarar cursor
  2. Abrir cursor
  3. Buscar linhas
  4. Fechar cursor
  5. Desalocar cursor

Observação importante


Por favor, tenha em mente que, de acordo com Vaidehi Pandere, os cursores são ponteiros que ocupam a memória do seu sistema – que de outra forma seria reservada para outros processos importantes. É por isso que percorrer um grande conjunto de resultados usando cursores geralmente não é a melhor ideia – a menos que haja uma razão legítima para fazer isso.

Para obter informações mais detalhadas sobre isso, sinta-se à vontade para consultar meu artigo Como usar cursores SQL para fins especiais.

Exemplo de cursor SQL


Primeiro, veremos um exemplo de como um cursor SQL pode ser usado para renomear objetos de banco de dados um por um.

Para criar um cursor SQL que precisamos, vamos configurar um banco de dados de exemplo para que possamos executar nossos scripts nele.

Configurar banco de dados de amostra (UniversityV3)


Execute o script a seguir para criar e preencher o banco de dados de amostra UniversityV3 com duas tabelas:
-- (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

Criar um cursor SQL para renomear tabelas (_Backup)


Agora considere atender à seguinte especificação usando um cursor:
  1. Precisamos adicionar '_Backup' aos nomes de todas as tabelas existentes em um banco de dados
  2. As tabelas que já possuem '_Backup' em seu nome não devem ser renomeadas

Vamos criar um cursor SQL para renomear todas as tabelas no banco de dados de exemplo adicionando '_Backup' ao nome de cada tabela, ao mesmo tempo garantindo que as tabelas contendo '_Backup' em seu nome não serão renomeadas novamente executando o seguinte 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

Execute o script de renomeação e visualize os resultados


Agora, pressione F5 no SSMS (SQL Server Management Studio) para executar o script e ver os resultados:



Atualizar os nomes das tabelas no explorador de objetos do SSMS mostra claramente que as alteramos com sucesso conforme especificado.

Vamos executar o script novamente pressionando F5 novamente e veja os resultados:


Criando um cursor SQL para redefinir _Backup Naming


Também precisamos criar um script que use um cursor SQL para reverter os nomes das tabelas que acabamos de alterar para as iniciais – faremos isso removendo ‘_Backup’ de seus nomes.

O script abaixo nos permitirá fazer exatamente isso:
-- 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

Execute o script de redefinição e veja os resultados


A execução do script mostra que os nomes das tabelas foram redefinidos com sucesso:



Esses foram os exemplos de alguns cenários em que é difícil evitar o uso de cursores SQL devido à natureza do requisito. No entanto, ainda é possível encontrar uma abordagem alternativa.

Alternativas do cursor SQL


Existem duas alternativas mais comuns para cursores SQL, então vamos ver cada uma delas em detalhes.

Alternativa 1:variáveis ​​de tabela


Uma dessas alternativas são as variáveis ​​de tabela.

As variáveis ​​de tabela, assim como as tabelas, podem armazenar vários resultados – mas com algumas limitações. De acordo com a documentação da Microsoft, uma variável de tabela é um tipo de dados especial usado para armazenar um conjunto de resultados para processamento posterior.

No entanto, lembre-se de que as variáveis ​​de tabela são melhor usadas com pequenos conjuntos de dados.

As variáveis ​​de tabela podem ser muito eficientes para consultas de pequena escala, pois funcionam como variáveis ​​locais e são limpas automaticamente ao sair do escopo.

Estratégia de variável de tabela:


Usaremos variáveis ​​de tabela em vez de cursores SQL para renomear todas as tabelas de um banco de dados seguindo estas etapas:
  1. Declare uma variável de tabela
  2. Armazene nomes e ids de tabelas na variável de tabela que declaramos
  3. Defina o contador como 1 e obtenha o número total de registros da variável da tabela
  4. Use um loop 'while' desde que o contador seja menor ou igual ao número total de registros
  5. Dentro do loop 'while', renomearemos as tabelas uma a uma, desde que ainda não tenham sido renomeadas e aumentando o contador para cada tabela

Código da variável da tabela:


Execute o seguinte script SQL que cria e usa uma variável de tabela para renomear tabelas:
-- 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

Execute o script e veja os resultados


Agora, vamos executar o script e verificar os resultados:


Alternativa 2:tabelas temporárias


Também podemos usar tabelas temporárias em vez de cursores SQL para iterar o conjunto de resultados uma linha por vez.

As tabelas temporárias estão em uso há muito tempo e fornecem uma excelente maneira de substituir cursores para grandes conjuntos de dados.

Assim como as variáveis ​​de tabela, as tabelas temporárias podem conter o conjunto de resultados para que possamos realizar as operações necessárias processando-o com um algoritmo de iteração, como um loop 'while'.

Estratégia de mesa temporária:


Usaremos uma tabela temporária para renomear todas as tabelas no banco de dados de exemplo seguindo estas etapas:
  1. Declarar uma tabela temporária
  2. Armazene nomes e ids de tabelas na tabela temporária que acabamos de declarar
  3. Defina o contador como 1 e obtenha o número total de registros da tabela temporária
  4. Use um loop 'while' desde que o contador seja menor ou igual ao número total de registros
  5. Dentro do loop 'while', renomeie as tabelas uma a uma, desde que ainda não tenham sido renomeadas e aumente o contador para cada tabela

Redefinir as tabelas


Precisamos redefinir os nomes das tabelas para sua forma inicial excluindo '_Backup' do final de seus nomes, portanto, execute novamente o script de redefinição que já escrevemos e usamos acima para que possamos aplicar outro método de renomeação de tabelas.

Código de tabela temporária:


Execute o seguinte script SQL para criar e usar uma tabela temporária para renomear todas as tabelas em nosso banco de dados:
-- 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

Execute o script e verifique a saída


Agora, vamos executar o script para visualizar os resultados:


Coisas para fazer


Agora que você está familiarizado com alternativas aos cursores SQL – como usar variáveis ​​de tabela e tabelas temporárias – tente fazer o seguinte para se sentir confortável em aplicar esse conhecimento na prática:
  1. Criar e renomear índices de todas as tabelas em um banco de dados de exemplo - primeiro por meio de um cursor e, em seguida, usando métodos alternativos (variáveis ​​de tabela e tabelas temporárias)
  2. Reverta os nomes das tabelas deste artigo para seus nomes iniciais usando métodos alternativos (tabelas temporárias e variáveis ​​de tabela)
  3. Você também pode consultar os primeiros exemplos em meu artigo Como usar cursores SQL para fins especiais e tentar preencher tabelas com muitas linhas e medir as estatísticas e o tempo das consultas para comparar o método básico do cursor com as alternativas