Documentação do PostgreSQL 8.0.0 | ||||
---|---|---|---|---|
Anterior | Início | Capítulo 35. PL/pgSQL - Linguagem procedural SQL | Fim | Próxima |
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.
Estão disponíveis dois comandos que permitem retornar dados de uma função: RETURN e RETURN NEXT.
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.
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.
As instruções IF permitem executar os comandos com base em certas condições. A linguagem PL/pgSQL possui cinco formas de IF:
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
IF ... THEN ... ELSEIF ... THEN ... ELSE
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;
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;
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.
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;
ELSEIF é um aliás para ELSIF.
Com as instruções LOOP, EXIT, WHILE e FOR pode-se fazer uma função PL/pgSQL repetir uma série de comandos.
[<<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 [ 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;
[<<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;
[<<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.
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.
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.