Cursor no SQL Server

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

Cenário prático

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.

tabela

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 

Conclusão

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.

Referências

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

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>