Cursores: O Bom, o Mau e o SQL…

O Bom

Uma simples query SQL permite visualizar informação (retornada em formato tabular), sendo essa informação lida por um qualquer programa (app, site, etc.). Mas o que acontece se quisermos que o próprio motor SQL trate a informação?

Consideremos uma tabela de colaboradores duma empresa. Todos os meses temos que processar o ordenado. Para isso executamos algo (e.g. um stored procedure), que tem toda a “magia” e complexidade lá dentro. Mas precisamos de executar tantas vezes quantos registos tivermos. Isso não é possível com um simples SELECT.

Aqui entram os cursores. São uma figura demoníaca muito mal tratada, mas que se virmos bem são até bastantes simples.

O que faz então um cursor? Um cursor executa uma certa query e permite ao programa ler o resultado registo a registo (sequencialmente) num ciclo, eventualmente fazendo múltiplas operações com cada registo.

O Mau

Nem tudo é bom em cursores, claro. A sintaxe “feia” é um dos problemas, mas quanto a isso nada a fazer (hashtag #ComeECala).

Outra, mais grave, é a dificuldade do motor de SQL em optimizar o pedido. Numa query SQL é fácil optimizar o acesso e obter os dados da forma mais eficiente globalmente. O problema é que os cursores pedem um resultado inicial e depois cada operação executada em cada registo retornado é tratada independentemente.

Daí a recomendação de não usar cursores em consultas em que uma simples (ou complexa) query resolva o problema, mas nas situações de execução individual (registo a registo) de tarefas não há outra opção.

O SQL

Um exemplo vale mil palavras… Vamos usar SQL Server e VB.Net mas será semelhante para outros motores de bases de dados e outras linguagens.

Começamos por criar alguns objectos na base de dados e testá-los.

-- Começamos por criar uma tabela simples
CREATE TABLE Cats
(
CatID      INT           NOT NULL PRIMARY KEY,
CatName    NVARCHAR(64)  NOT NULL,
CatYear    INT           NOT NULL,
);
GO

-- De seguida inserimos alguns registos
INSERT INTO Cats
(CatID, CatName, CatYear)
VALUES
( 1, 'Grumpy Cat',                   2012),
( 2, 'Schrody (Schrödinger''s cat)', 1935), /*
The name is correct and at the same time is in-
correct - until someone asks the cat! */
( 3, 'Garfield',                     1978),
( 4, 'Hobbes',                       1985),
( 5, 'Mr.Bigglesworth',              1997),
( 6, 'Sylvester',                    1941),
( 7, 'Simba',                        1994),
( 8, 'Tigger',                       1928),
( 9, 'Heathcliff',                   1973),
(10, 'Simon''s Cat',                 2008),
(11, 'Spot (Data''s cat)',           1991),
(12, 'Top Cat',                      1961),
(13, 'Fofinha',                      2001),
(14, 'Leoa (the ''Mini-Cat'')',      2008),
(15, 'Pantyusha (Panteleymon)',      1900);
GO

-- A curiosidade matou o gato...
SELECT * FROM Cats;
GO

-- De seguida criamos um SP para executar
CREATE PROCEDURE USP_CatActions
    @CatID   INT,
    @CatName NVARCHAR(64) AS
      Print (CONCAT('A fazer coisas felinas com
       "', @CatName, '" (ID=', @CatID, ')...'));
    -- etc...
GO
-- Executar registo a registo tão fácil
EXEC USP_CatActions @CatID = 1, @CatName =
                                    'Grumpy Cat';
GO

O resultado da execução do stored procedure é:

A fazer coisas felinas com “Grumpy Cat” (ID=1)…

[...]

Leia o artigo completo na edição 57 da Revista PROGRAMAR

Publicado na edição 57 (PDF) da Revista PROGRAMAR.