quarta-feira, 20 de março de 2019

Iterações em Conjuntos de Dados Com Cursores e Sem Cursores

Em SQL existem duas formas de fazer iterações sobre as linhas de uma tabela, acessando a cada ciclo um registro específico.


1-Acesso iterativo/procedural através do uso de CURSOR: Um objeto que carrega a tabela em memória e permite a iteração linha a linha através de variáveis.

2-Set-Based declarativo: Acessa os dados utilizando os princípios relacionais que aplicam as regras da teoria de conjuntos, que opera com tabelas como conjuntos de entradas

A linguagem de consulta SQL é diferente das linguagens de programação tradicionais, ela é essencialmente declarativa e não procedural, isso significa que você deve se preocupar em definir o que você quer e não como obter o que você quer.

Na teoria, é sempre indicado utilizar abordagem que utiliza operações Set-Based em tabelas e evitar operações com linhas individuais com cursores devido a problemas de performance.

Realizar operações com cursores acaba quebrando o princípio da teoria de conjuntos com o qual os bancos de dados relacionais foram criados, pois além de definir o quê será retornado é necessário definir como operar sobre esse dado.

Por outro lado, quando é usada a abordagem de conjuntos, não é possível garantir a ordenação precisa das linhas retornadas a menos que seja usado cuidadosamente a cláusula ORDER BY, para garantir o retorno de valores únicos para a ordenação. Já com iterações com cursores é possível garantir o controle total da ordenação.


Iteração com Cursores


Cursores são estruturas da linguagem T-SQL que permitem o processamento das linhas retornadas por uma consulta (SELECT), através de estruturas complexas de programação, como repetições ou  comandos condicionais.
O exemplo básico de cursor consiste em uma repetição (loop) onde um mesmo conjunto de comandos é executado para todas as linhas do retorno de uma consulta.

A sequência de declaração: DECLARE > OPEN > FETCH (loop) > CLOSE > DEALLOCATE

DECLARE @curcustid AS INT;

-- Cursor para percorrer os nomes dos objetos
DECLARE cust_cursor CURSOR FAST_FORWARD FOR 

  SELECT custid   FROM Sales.Customers;

  -- Abrindo Cursor para leitura
 OPEN cust_cursor;

-- Lendo a primeira linha e atribuindo às variáveis que serão atualizadas a cada iteração
FETCH NEXT FROM cust_cursor INTO @curcustid;

-- Percorrendo linhas do cursor (enquanto houver)
WHILE @@FETCH_STATUS = 0
BEGIN 

    EXEC Sales.ProcessCustomer @custid = @curcustid;

    FETCH NEXT FROM cust_cursor INTO @curcustid;
END;

-- Fechando Cursor para leitura
CLOSE cust_cursor;

-- Desalocando o cursor
DEALLOCATE cust_cursor;


Algumas considerações:
Um cursor deve estar sempre associado a uma consulta, especificada ao declarar o cursor.
O comando FETCH popula as variáveis recebidas como parâmetro com os valores da próxima linha da consulta a ser lida. O número de variáveis passadas como parâmetro deve ser igual ao número de colunas retornadas na consulta associada ao cursor.
A variável global @@FETCH_STATUS retorna o resultado da última operação FETCH executada por um cursor na conexão.
O status 0 significa que o comando FETCH retornou uma linha, qualquer outro resultado significa que não houve linha retornada.
Cursores são estruturas relativamente lentas se comparadas ao desempenho de consultas do banco. O uso descuidado desse método pode causar sérios problemas de performance.


Set-Based

Para obter o mesmo efeito e cumprir o mesmo tipo de tarefa usando somente instruções padrão sql sem usar cursores, você pode usar as cláusulas ORDER BY em conjunto com TOP(1) e um loop com WHILE.

Exemplo de iteração sem usar cursor:

DECLARE @curcustid AS INT;

--Seleciona o cliente com o menor número de código
SET @curcustid = (SELECT TOP (1) custid FROM Sales.Customers ORDER BY custid);

--Itera enquanto houver clientes com valores maiores que o atual
WHILE @curcustid IS NOT NULL
BEGIN
      EXEC Sales.ProcessCustomer @custid = @curcustid;
   
    --Atribui o valor de código do próximo cliente
     SET @curcustid = (SELECT TOP (1) custid FROM Sales.Customers WHERE custid > @curcustid ORDER BY custid);
END;
GO

Nestes casos é necessário criar indice para a coluna custid, caso contrário a tabela será toda escanada e a performance será pior do que um cursor.

Nenhum comentário:

Postar um comentário