O termo cursor na ciência da computação se refere a uma estrutura de controle para acesso transversal em conjuntos de dados. Apesar dessa definição bonita, os cursores representam um conceito simples e útil em banco de dados para executar operações linha a linha de tabelas.
A sintaxe dele é a seguinte.
DECLARE <cursor> CURSOR FOR SELECT ... FROM ... OPEN <cursor> DECLARE <variável> INTEGER FETCH NEXT FROM <cursor> INTO <variável> WHILE @@FETCH_STATUS = 0 BEGIN <operações com a variável> FETCH NEXT FROM <cursor> INTO <variável> END CLOSE <cursor> DEALLOCATE <cursor>
Veja esse exemplo completo, com comentários, para entender como ele funciona.
-- Criação de uma tabela para testes DECLARE @TabelaNomes TABLE ( Nome VARCHAR(50) ) -- Massa de dados para testes INSERT INTO @TabelaNomes VALUES ('Fulano'), ('Beltrano'), ('Ciclano') -- Declaração do cursor DECLARE CursorDeNomes CURSOR FOR SELECT [Nome] FROM @TabelaNomes ORDER BY [Nome] -- Inicialização -- O cursor ficará pronto para ser usado OPEN CursorDeNomes -- Declaração de variáveis que armazenarão -- cada linha de retorno do cursor DECLARE @Nome VARCHAR(50) -- Leitura da próxima linha do cursor (neste caso é a primeira linha) -- As variáveis incluídas após a cláusula INTO devem ser do mesmo tipo -- e estarem na mesma ordem do SELECT do cursor FETCH NEXT FROM CursorDeNomes INTO @Nome -- Sempre que o valor do fetch status for igual a 0, -- a leitura da linha teve sucesso e, portanto, as variáveis -- estão com seus valores atribuídos -- Se o valor do fetch next for diferente de 0, significa -- que o cursor não tem mais linhas para percorrer WHILE @@FETCH_STATUS = 0 BEGIN PRINT @Nome -- Leitura da próxima linha do cursor FETCH NEXT FROM CursorDeNomes INTO @Nome END -- Após todo o cursor ser percorrido, -- deve ser feito o seu encerramento CLOSE CursorDeNomes DEALLOCATE CursorDeNomes
Resultado
(3 row(s) affected) Beltrano Ciclano Fulano
Vamos experimentar uma situação prática. Você possui uma tabela Funcionario no seu banco de dados com os campos Id, Nome, Cargo, Salario e Desempenho; e precisa executar uma das stored procedures Bonificar e Promover, baseado no desempenho de cada funcionário. Se o desempenho do funcionário for superior ou igual a 90%, o funcionário deve ser promovido. Se for menor que 90% e maior ou igual a 70%, deve receber uma bonificação.
Parece um caso simples, e de fato, ele é. Mas surge um complicador neste problema, porque ambas as stored procedures recebem um Id de funcionário como parâmetro e não possuem retorno. Para piorar, não lhe deram permissão de alterar essas stored procedures.
Como seria possível executá-las para cada funcionário que esteja dentro das faixas de desempenho a serem recompensadas? Uma forma de fazer isso é através de cursor, de modo a ler, verificar e enviar os Ids de funcionários individualmente.
Na linguagem T-SQL do MS SQL Server, o código ficaria assim:
-- Declaração DECLARE FuncionarioCursor CURSOR FOR SELECT [Id], [Desempenho] FROM dbo.Funcionario ORDER BY [Nome] -- Inicialização OPEN FuncionarioCursor -- Variáveis DECLARE @Id INT DECLARE @Desempenho INT -- Leitura da primeira linha do cursor FETCH NEXT FROM FuncionarioCursor INTO @Id, @Desempenho -- Início do laço WHILE @@FETCH_STATUS = 0 BEGIN -- Operações IF @Desempenho >= 90 EXEC dbo.Promover @Id ELSE IF @Desempenho >= 70 EXEC dbo.Bonificar @Id -- Leitura da próxima linha do cursor FETCH NEXT FROM FuncionarioCursor INTO @Id, @Desempenho END -- Encerramento CLOSE FuncionarioCursor DEALLOCATE FuncionarioCursor
Cursores oferecem grande poder para o desenvolvedor e são uma alternativa para situações aonde uma lógica precisa ser aplicada na linha. Entretanto, a performance de cursores é ruim quando comparadas a outras estruturas de controle e devido a quantidade de código necessária eles tendem a ser difíceis de dar manutenção.
Em muitos casos, é fácil substituir cursores por alguma operação de conjunto, mas infelizmente encontramos usos assim quando as pessoas não estão habituadas ao paradigma relacional ou não conhecem que é possível aplicar joins em updates/deletes ou usar o resultado de um select na cláusula insert.
A recomendação geral é que cursores devam ser evitados sempre que possível e, quando não houver outras alternativas, que eles sejam implementados com responsabilidade.
DECLARE CURSOR (Transact-SQL)
https://msdn.microsoft.com/en-us/library/ms180169.aspx
FETCH (Transact-SQL)
https://msdn.microsoft.com/en-us/library/ms180152.aspx