35.7. Estruturas de controle

PostgreSQL 14.5: Estruturas de controle

As estruturas de controle provavelmente são a parte mais útil (e mais importante) da linguagem PL/pgSQL. Com as estruturas de controle do PL/pgSQL os dados do PostgreSQL podem ser manipulados de uma forma muita flexível e poderosa.

35.7.1. Retorno de uma função

Estão disponíveis dois comandos que permitem retornar dados de uma função: RETURN e RETURN NEXT.

35.7.1.1. RETURN

RETURN expressão;

O comando RETURN com uma expressão termina a função e retorna o valor da expressão para quem chama. Esta forma é utilizada pelas funções do PL/pgSQL que não retornam conjunto.

Qualquer expressão pode ser utilizada para retornar um tipo escalar. O resultado da expressão é automaticamente convertido no tipo de retorno da função conforme descrito nas atribuições. Para retornar um valor composto (linha), deve ser escrita uma variável registro ou linha como a expressão.

O valor retornado pela função não pode ser deixado indefinido. Se o controle atingir o final do bloco de nível mais alto da função sem atingir uma instrução RETURN, ocorrerá um erro em tempo de execução.

Se a função for declarada como retornando void, ainda assim deve ser especificada uma instrução RETURN; mas neste caso a expressão após o comando RETURN é opcional, sendo ignorada caso esteja presente.

35.7.1.2. RETURN NEXT

RETURN NEXT expressão;

Quando uma função PL/pgSQL é declarada como retornando SETOF algum_tipo, o procedimento a ser seguido é um pouco diferente. Neste caso, os itens individuais a serem retornados são especificados em comandos RETURN NEXT, e um comando RETURN final, sem nenhum argumento, é utilizado para indicar que a função chegou ao fim de sua execução. O comando RETURN NEXT pode ser utilizado tanto com tipos de dado escalares quanto compostos; no último caso toda uma "tabela" de resultados é retornada.

As funções que utilizam RETURN NEXT devem ser chamadas da seguinte maneira:

SELECT * FROM alguma_função();

Ou seja, a função deve ser utilizada como uma fonte de tabela na cláusula FROM.

Na verdade, o comando RETURN NEXT não faz o controle sair da função: simplesmente salva o valor da expressão. Em seguida, a execução continua na próxima instrução da função PL/pgSQL. O conjunto de resultados é construído se executando comandos RETURN NEXT sucessivos. O RETURN final, que não deve possuir argumentos, faz o controle sair da função.

Nota: A implementação atual de RETURN NEXT para o PL/pgSQL armazena todo o conjunto de resultados antes de retornar da função, conforme foi mostrado acima. Isto significa que, se a função PL/pgSQL produzir um conjunto de resultados muito grande, o desempenho será ruim: os dados serão escritos em disco para evitar exaurir a memória, mas a função não retornará antes que todo o conjunto de resultados tenha sido gerado. Uma versão futura do PL/pgSQL deverá permitir aos usuários definirem funções que retornam conjuntos que não tenham esta limitação. Atualmente, o ponto onde os dados começam a ser escritos em disco é controlado pela variável de configuração work_mem. Os administradores que possuem memória suficiente para armazenar conjuntos de resultados maiores, devem considerar o aumento deste parâmetro.

35.7.2. Condicionais

As instruções IF permitem executar os comandos com base em certas condições. A linguagem PL/pgSQL possui cinco formas de IF:

35.7.2.1. IF-THEN

IF expressão_booleana THEN
    instruções
END IF;

As instruções IF-THEN são a forma mais simples de IF. As instruções entre o THEN e o END IF são executadas se a condição for verdade. Senão, são saltadas.

Exemplo:

IF v_id_usuario <> 0 THEN
    UPDATE usuarios SET email = v_email WHERE id_usuario = v_id_usuario;
END IF;

35.7.2.2. IF-THEN-ELSE

IF expressão_booleana THEN
    instruções
ELSE
    instruções
END IF;

As instruções IF-THEN-ELSE ampliam o IF-THEN permitindo especificar um conjunto alternativo de instruções a serem executadas se a condição for avaliada como falsa.

Exemplos:

IF id_pais IS NULL OR id_pais = ''
THEN
    RETURN nome_completo;
ELSE
    RETURN hp_true_filename(id_pais) || '/' || nome_completo;
END IF;
IF v_contador > 0 THEN
    INSERT INTO contador_de_usuários (contador) VALUES (v_contador);
    RETURN 't';
ELSE
    RETURN 'f';
END IF;

35.7.2.3. IF-THEN-ELSE IF

As instruções IF podem ser aninhadas, como no seguinte exemplo:

IF linha_demo.sexo = 'm' THEN
    sexo_extenso := 'masculino';
ELSE
    IF linha_demo.sexo = 'f' THEN
        sexo_extenso := 'feminino';
    END IF;
END IF;

Na verdade, quando esta forma é utilizada uma instrução IF está sendo aninhada dentro da parte ELSE da instrução IF externa. Portanto, há necessidade de uma instrução END IF para cada IF aninhado, mais um para o IF-ELSE pai. Embora funcione, cresce de forma tediosa quando existem muitas alternativas a serem verificadas. Por isso existe a próxima forma.

35.7.2.4. IF-THEN-ELSIF-ELSE

IF expressão_booleana THEN
    instruções
[ ELSIF expressão_booleana THEN
    instruções
[ ELSIF expressão_booleana THEN
    instruções
    ...]]
[ ELSE
    instruções ]
END IF;

A instrução IF-THEN-ELSIF-ELSE fornece um método mais conveniente para verificar muitas alternativas em uma instrução. Formalmente equivale aos comandos IF-THEN-ELSE-IF-THEN aninhados, mas somente necessita de um END IF.

Abaixo segue um exemplo:

IF numero = 0 THEN
    resultado := 'zero';
ELSIF numero > 0 THEN
    resultado := 'positivo';
ELSIF numero < 0 THEN
    resultado := 'negativo';
ELSE
    -- hmm, a única outra possibilidade é que o número seja nulo
    resultado := 'NULL';
END IF;

35.7.2.5. IF-THEN-ELSEIF-ELSE

ELSEIF é um aliás para ELSIF.

35.7.3. 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.

35.7.3.1. 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.

35.7.3.2. 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.

Exemplos:

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;

35.7.3.3. WHILE

[<<rótulo>>]
WHILE expressão LOOP
    instruções
END LOOP;

A instrução WHILE repete uma seqüência de instruções enquanto a expressão de condição for avaliada como verdade. A condição é verificada logo antes de cada entrada no corpo do laço.

Por exemplo:

WHILE quantia_devida > 0 AND saldo_do_certificado_de_bonus > 0 LOOP
    -- algum processamento
END LOOP;

WHILE NOT expressão_booleana LOOP
    -- algum processamento
END LOOP;

35.7.3.4. FOR (variação inteira)

[<<rótulo>>]
FOR nome IN [ REVERSE ] expressão .. expressão LOOP
    instruções
END LOOP;

Esta forma do FOR cria um laço que interage num intervalo de valores inteiros. A variável nome é definida automaticamente como sendo do tipo integer, e somente existe dentro do laço. As duas expressões que fornecem o limite inferior e superior do intervalo são avaliadas somente uma vez, ao entrar no laço. Normalmente o passo da interação é 1, mas quando REVERSE é especificado se torna -1.

Alguns exemplos de laços FOR inteiros:

FOR i IN 1..10 LOOP
    -- algum processamento
    RAISE NOTICE 'i é %', i;
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- algum processamento
END LOOP;

Se o limite inferior for maior do que o limite superior (ou menor, no caso do REVERSE), o corpo do laço não é executado nenhuma vez. Nenhum erro é gerado.

35.7.4. 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.

Nota: Atualmente o analisador da linguagem PL/pgSQL faz distinção entre os dois tipos de laços FOR (inteiro e resultado de consulta), verificando se aparece .. fora de parênteses entre IN e LOOP. Se não for encontrado .., então o laço é assumido como sendo um laço sobre linhas. Se .. for escrito de forma errada, pode causar uma reclamação informando que "a variável do laço, para laço sobre linhas, deve ser uma variável registro ou linha", em vez de um simples erro de sintaxe como poderia se esperar.

35.7.5. 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.

Dica: Custa significativamente mais entrar e sair de um bloco que contém a cláusula EXCEPTION que de um bloco que não contém esta cláusula. Portanto, a cláusula EXCEPTION só deve ser utilizada quando for necessária.

SourceForge.net Logo CSS válido!