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:
- Declarar cursor
- Abrir cursor
- Buscar linhas
- Fechar cursor
- 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:
- Precisamos adicionar '_Backup' aos nomes de todas as tabelas existentes em um banco de dados
- 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:
- Declare uma variável de tabela
- Armazene nomes e ids de tabelas na variável de tabela que declaramos
- Defina o contador como 1 e obtenha o número total de registros da variável da tabela
- Use um loop 'while' desde que o contador seja menor ou igual ao número total de registros
- 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:
- Declarar uma tabela temporária
- Armazene nomes e ids de tabelas na tabela temporária que acabamos de declarar
- Defina o contador como 1 e obtenha o número total de registros da tabela temporária
- Use um loop 'while' desde que o contador seja menor ou igual ao número total de registros
- 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:
- 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)
- Reverta os nomes das tabelas deste artigo para seus nomes iniciais usando métodos alternativos (tabelas temporárias e variáveis de tabela)
- 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