Laços simples

Com as instruções LOOP, EXIT, WHILE e FOR pode-se fazer uma função PL/pgSQL repetir uma série de comandos.

LOOP

[<<rótulo>>]
LOOP
    instruções
END LOOP;

A instrução LOOP define um laço incondicional, repetido indefinidamente até ser terminado por uma instrução EXIT ou RETURN. Nos laços aninhados pode ser utilizado um rótulo opcional na instrução EXIT para especificar o nível de aninhamento que deve ser terminado.

EXIT

EXIT [ rótulo ] [ WHEN expressão ];

Se não for especificado nenhum rótulo, o laço mais interno será terminado, e a instrução após o END LOOP será executada a seguir. Se o rótulo for fornecido, deverá ser o rótulo do nível corrente, ou o rótulo de algum nível externo ao laço ou bloco aninhado. Nesse momento o laço ou bloco especificado será terminado, e o controle continuará na instrução após o END correspondente ao laço ou bloco.

Quando WHEN está presente, a saída do laço ocorre somente se a condição especificada for verdadeira, senão o controle passa para a instrução após o EXIT.

Pode ser utilizado EXIT para causar uma saída prematura de qualquer tipo de laço; não está limitado aos laços incondicionais.

LOOP
    -- algum processamento
    IF contador > 0 THEN
        EXIT;  -- sair do laço
    END IF;
END LOOP;
LOOP
    -- algum processamento
    EXIT WHEN contador > 0;  -- mesmo resultado do exemplo acima
END LOOP;
BEGIN
    -- algum processamento
    IF estoque > 100000 THEN
        EXIT;  -- causa a saída do bloco BEGIN
    END IF;
END;

Laço através do resultado da consulta

Utilizando um tipo diferente de laço FOR, é possível interagir através do resultado de uma consulta e manipular os dados.

A sintaxe é:

[<<rótulo>>]
FOR registro_ou_linha IN comando LOOP
    instruções
END LOOP;

Cada linha de resultado do comando (que deve ser um SELECT) é atribuída, sucessivamente, à variável registro ou linha, e o corpo do laço é executado uma vez para cada linha. Abaixo segue um exemplo:

CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    PERFORM cs_log('Atualização das visões materializadas...');
    FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP
        -- Agora "mviews" possui um registro de cs_materialized_views
        PERFORM cs_log('Atualizando a visão materializada ' || quote_ident(mviews.mv_name) || ' ...');
        EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name);
        EXECUTE 'INSERT INTO ' || quote_ident(mviews.mv_name) || ' ' || mviews.mv_query;
    END LOOP;
    PERFORM cs_log('Fim da atualização das visões materializadas.');
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Se o laço for terminado por uma instrução EXIT, o último valor de linha atribuído ainda é acessível após o laço.

A instrução FOR-IN-EXECUTE é outra forma de interagir sobre linhas:

[<<rótulo>>]
FOR registro_ou_linha IN EXECUTE texto_da_expressão LOOP
    instruções
END LOOP;

Esta forma é semelhante à anterior, exceto que o código fonte da instrução SELECT é especificado como uma expressão cadeia de caracteres, que é avaliada e replanejada a cada entrada no laço FOR. Isto permite ao programador escolher entre a velocidade da consulta pré-planejada e a flexibilidade da consulta dinâmica, da mesma maneira que na instrução EXECUTE pura.

Captura de erros

Por padrão, qualquer erro que ocorra em uma função PL/pgSQL interrompe a execução da função, e também da transação envoltória. É possível capturar e se recuperar de erros utilizando um bloco BEGIN com a cláusula EXCEPTION. A sintaxe é uma extensão da sintaxe normal do bloco BEGIN:

[ <<rótulo>> ]
[ DECLARE
    declarações ]
BEGIN
    instruções
EXCEPTION
    WHEN condição [ OR condição ... ] THEN
        instruções_do_tratador
    [ WHEN condição [ OR condição ... ] THEN
          instruções_do_tratador
      ... ]
END;

Caso não ocorra nenhum erro, esta forma do bloco simplesmente executa todas as instruções, e depois o controle passa para a instrução seguinte ao END. Mas se acontecer algum erro dentro de instruções, o processamento das instruções é abandonado e o controle passa para a lista de EXCEPTION. É feita a procura na lista da primeira condição correspondendo ao erro encontrado. Se for encontrada uma correspondência, as instruções_do_tratador correspondentes são executadas, e o controle passa para a instrução seguinte ao END. Se não for encontrada nenhuma correspondência, o erro se propaga para fora como se a cláusula EXCEPTION não existisse: o erro pode ser capturado por um bloco envoltório contendo EXCEPTION e, se não houver nenhum, o processamento da função é interrompido.

O nome da condição pode ser qualquer um dos mostrados no Apêndice A. Um nome de categoria corresponde a qualquer erro desta categoria. O nome de condição especial OTHERS corresponde a qualquer erro, exceto QUERY_CANCELED (É possível, mas geralmente não aconselhável, capturar QUERY_CANCELED por nome). Não há diferença entre letras maiúsculas e minúsculas nos nomes das condições.

Caso ocorra um novo erro dentro das instruções_do_tratador selecionadas, este não poderá ser capturado por esta cláusula EXCEPTION, mas é propagado para fora. Uma cláusula EXCEPTION envoltória pode capturá-lo.

Quando um erro é capturado pela cláusula EXCEPTION, as variáveis locais da função PL/pgSQL permanecem como estavam quando o erro ocorreu, mas todas as modificações no estado persistente do banco de dados dentro do bloco são desfeitas. Como exemplo, consideremos este fragmento de código:

    INSERT INTO minha_tabela(nome, sobrenome) VALUES('Tom', 'Jones');
    BEGIN
        UPDATE minha_tabela SET nome = 'Joe' WHERE sobrenome = 'Jones';
        x := x + 1;
        y := x / 0;
    EXCEPTION
        WHEN division_by_zero THEN
            RAISE NOTICE 'capturado division_by_zero';
            RETURN x;
    END;

Quando o controle chegar à atribuição de y, vai falhar com um erro de division_by_zero. Este erro será capturado pela cláusula EXCEPTION. O valor retornado na instrução RETURN será o valor de x incrementado, mas os efeitos do comando UPDATE foram desfeitos. Entretanto, o comando INSERT que precede o bloco não é desfeito e, portanto, o resultado final no banco de dados é Tom Jones e não Joe Jones.

Erros e mensagens

A instrução RAISE é utilizada para gerar mensagens informativas e causar erros.

RAISE nível 'formato' [, variável [, ...]];

Os níveis possíveis são DEBUG, LOG, INFO, NOTICE, WARNING, e EXCEPTION. O nível EXCEPTION causa um erro (que normalmente interrompe a transação corrente); os outros níveis apenas geram mensagens com diferentes níveis de prioridade. Se as mensagens de uma determinada prioridade são informadas ao cliente, escritas no log do servidor, ou as duas coisas, é controlado pelas variáveis de configuração log_min_messages e client_min_messages. Para obter informações adicionais deve ser consultada a Seção 16.4.

Dentro da cadeia de caracteres de formatação, o caractere % é substituído pela representação na forma de cadeia de caracteres do próximo argumento opcional. Deve ser escrito %% para produzir um % literal. Deve ser observado que atualmente os argumentos opcionais devem ser variáveis simples, e não expressões, e o formato deve ser um literal cadeia de caracteres simples.

Neste exemplo o valor de v_job_id substitui o caractere % na cadeia de caracteres:

RAISE NOTICE 'Chamando cs_create_job(%)', v_job_id;

Este exemplo interrompe a transação com a mensagem de erro fornecida:

RAISE EXCEPTION 'ID inexistente --> %', id_usuario;

Atualmente RAISE EXCEPTION sempre gera o mesmo código SQLSTATE, P0001, não importando a mensagem com a qual seja chamado. É possível capturar esta exceção com EXCEPTION ... WHEN RAISE_EXCEPTION THEN ..., mas não há como diferenciar um RAISE de outro.

PSQLG - Loop

By walternascimento