As estruturas de controle são provavelmente a parte mais útil (e importante) do PL/pgSQL. Com as estruturas de controle do PL/pgSQL, é possível manipular os dados do PostgreSQL de uma maneira muito flexível e poderosa.
Existem dois comandos disponíveis que permitem retornar dados de uma
função: RETURN e RETURN NEXT.
RETURN expressão;
RETURN com uma expressão termina a função e
retorna o valor da expressão para
quem efetuou a chamada.
Esta forma é usada em funções PL/pgSQL
que não retornam um conjunto.
Em uma função que retorna um tipo de dados escalar, o resultado da expressão será automaticamente convertido no tipo de dados retornado pela função, conforme descrito para atribuições. Mas, para retornar um valor composto (linha), deve ser escrita uma expressão que forneça exatamente o conjunto de colunas especificado, podendo exigir o uso de conversão explícita.
Se a função for declarada com parâmetros de saída, deve ser escrito
apenas RETURN sem a expressão. Serão retornados
os valores correntes das variáveis dos parâmetros de saída.
Se a função for declarada retornando void pode ser
usada uma instrução RETURN para sair da função
antecipadamente; mas não deve ser escrita uma expressão após o
RETURN.
O valor retornado pela função não pode ser deixado indefinido.
Se o controle atingir o final do bloco de nível superior da função
sem atingir uma instrução RETURN, ocorrerá um
erro de tempo de execução.
No entanto, esta restrição não se aplica a funções com parâmetros
de saída e funções que retornam void.
Nesses casos, uma instrução RETURN é executada
automaticamente se o bloco de nível superior terminar.
Alguns exemplos:
-- funções retornando tipo de dados escalar RETURN 1 + 2; RETURN variável_escalar; -- funções retornando tipo de dados composto RETURN variável_tipo_composto; RETURN (1, 2, 'três'::text); -- as colunas devem ser convertidas para os tipos corretos
RETURN NEXTexpressão; RETURN QUERYconsulta; RETURN QUERY EXECUTEcadeia_de_caracteres_de_comando[ USINGexpressão[, ... ] ];
Quando a função PL/pgSQL é declarada
retornando SETOF ,
o procedimento a seguir é um pouco diferente.
Neste caso, os itens individuais a serem retornados são especificados
por uma sequência de comandos algumtipoRETURN NEXT ou
RETURN QUERY e, em seguida, um comando final
RETURN sem nenhum argumento é usado para
indicar que a função terminou sua execução.
RETURN NEXT pode ser usado com tipos de dados
escalares e compostos; com um resultado de tipo composto, será
retornada uma “tabela” inteira de resultados.
RETURN QUERY anexa os resultados da execução
de uma consulta ao conjunto de resultados da função.
RETURN NEXT e RETURN QUERY
podem ser livremente misturados na mesma função retornando conjunto,
caso em que seus resultados são concatenados.
RETURN NEXT e RETURN QUERY,
na verdade, não causam o retorno da função — apenas
anexam zero ou mais linhas ao conjunto de resultados da função.
A execução então prossegue para a próxima instrução da função
PL/pgSQL.
À medida que sucessivos comandos RETURN NEXT ou
RETURN QUERY são executados, o conjunto de
resultados é construído.
Um RETURN final, que não deve ter nenhum
argumento, faz com que o controle saia da função
(ou basta apenas deixar o controle chegar ao final da função).
RETURN QUERY possui a variante
RETURN QUERY EXECUTE, que especifica a consulta
a ser executada dinamicamente.
Podem ser inseridas expressões de parâmetro na cadeia de
caracteres da consulta computada por meio de USING,
da mesma forma que no comando EXECUTE.
Se a função for declarada com parâmetros de saída, deve ser
escrito apenas RETURN NEXT, sem expressão.
A cada execução, os valores correntes da(s) variável(is) de parâmetro de
saída são salvos para retornar no final como uma linha de resultado.
Note que se deve declarar a função como retornando
SETOF record quando houver vários parâmetros de
saída, ou SETOF
quando houver apenas um parâmetro de saída do tipo
algumtipoalgumtipo, para criar uma função
que retorna conjunto com parâmetros de saída.
A seguir está um exemplo de função usando RETURN NEXT:
CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'três');
INSERT INTO foo VALUES (4, 5, 'seis');
CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$CORPO$
DECLARE
r foo%rowtype;
BEGIN
FOR r IN
SELECT * FROM foo WHERE fooid > 0
LOOP
-- pode ser feito algum processamento aqui
RETURN NEXT r; -- retornar a linha corrente do SELECT
END LOOP;
RETURN;
END;
$CORPO$
LANGUAGE plpgsql;
SELECT * FROM get_all_foo();
fooid | foosubid | fooname
-------+----------+---------
1 | 2 | três
4 | 5 | seis
(2 linhas)
A seguir é mostrado o exemplo de uma função usando
RETURN QUERY:
CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$CORPO$
BEGIN
RETURN QUERY SELECT flightid
FROM flight
WHERE flightdate >= $1
AND flightdate < ($1 + 1);
-- Como a execução da função ainda não terminou, pode-se verificar
-- se foram retornadas linhas e, caso contrário, lançar uma exceção.
IF NOT FOUND THEN
RAISE EXCEPTION 'Nenhum voo em %.', $1;
END IF;
RETURN;
END;
$CORPO$
LANGUAGE plpgsql;
-- Retorna os voos disponíveis, ou lança uma
-- exceção caso não haja voos disponíveis.
SELECT * FROM get_available_flightid(CURRENT_DATE);
A implementação corrente de RETURN NEXT e
RETURN QUERY armazena todo o conjunto de
resultados antes de retornar da função, conforme discutido acima,
significando que, se a função PL/pgSQL
produzir um conjunto de resultados muito grande, o desempenho poderá
ser ruim: os dados serão escritos em disco para evitar o esgotamento
da memória, mas a função em si não retornará até que todo o conjunto
de resultados tenha sido gerado.
Uma versão futura do PL/pgSQL poderá
permitir que os usuários definam funções de retorno de conjunto
que não tenham esta limitação.
No momento, o ponto em que os dados começam a ser escritos em
disco é controlado pelo parâmetro de configuração
work_mem.
Os administradores que possuem máquinas com memória suficiente
para armazenar conjuntos de resultados maiores na memória, devem
considerar o aumento desse parâmetro.
Um procedimento não tem valor retornado.
Um procedimento pode, portanto, terminar sem uma instrução
RETURN.
Se for desejado usar uma instrução RETURN para
sair do código antecipadamente, deve ser escrito apenas
RETURN, sem a expressão.
Se o procedimento tiver parâmetros de saída, os valores finais das variáveis de parâmetro de saída serão retornados a quem efetuou a chamada.
Uma função, procedimento ou bloco DO escrito em
PL/pgSQL pode chamar um procedimento
usando a instrução CALL.
Os parâmetros de saída são tratados de forma diferente de como
funcionam na instrução CALL do
SQL puro.
Cada parâmetro OUT ou INOUT
do procedimento deve corresponder a uma variável na instrução
CALL, e ao retornar o procedimento atribui a
esta variável seu valor de retorno. Por exemplo:
CREATE PROCEDURE triplicar(INOUT x int) AS
$CORPO$
BEGIN
x := x * 3;
END;
$CORPO$
LANGUAGE plpgsql;
DO $$
DECLARE myvar int := 5;
BEGIN
CALL triplicar(myvar);
RAISE NOTICE 'myvar = %', myvar; -- mostra 15
END;
$$;
A variável correspondente a um parâmetro de saída pode ser uma variável simples, ou um campo de uma variável de tipo composto. No momento, não pode ser um elemento de uma matriz.
As instruções IF e CASE
permitem executar comandos segundo determinadas condições.
O PL/pgSQL possui três formas de IF:
IF ... THEN ... END IF
IF ... THEN ... ELSE ... END IF
IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF
e duas formas de CASE:
CASE ... WHEN ... THEN ... ELSE ... END CASE
CASE WHEN ... THEN ... ELSE ... END CASE
IFexpressão_booleanaTHENinstruçõesEND IF;
As instruções IF-THEN são a forma mais
simples de IF.
As instruções entre THEN e
END IF são executadas se a condição resultar
em verdade. Caso contrário, são ignoradas.
Exemplo:
IF v_user_id <> 0 THEN
UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;
IFexpressão_booleanaTHENinstruçõesELSEinstruçõesEND IF;
As instruções IF-THEN-ELSE são uma adição ao
IF-THEN, permitindo especificar um
conjunto alternativo de instruções que devem ser executadas
se a condição não resultar em verdade. (Note que isto
inclui o caso em que a condição é avaliada como nula.)
Exemplos:
IF parentid IS NULL OR parentid = ''
THEN
RETURN fullname;
ELSE
RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;
IF v_count > 0 THEN
INSERT INTO users_count (count) VALUES (v_count);
RETURN 't';
ELSE
RETURN 'f';
END IF;
IFexpressão_booleanaTHENinstruções[ ELSIFexpressão_booleanaTHENinstruções[ ELSIFexpressão_booleanaTHENinstruções... ] ] [ ELSEinstruções] END IF;
Às vezes, há mais do que apenas duas alternativas.
IF-THEN-ELSIF fornece um método conveniente
para verificar várias alternativas sucessivamente.
As condições IF são testadas sucessivamente,
até que a primeira que resultar em verdade seja encontrada.
Em seguida, a(s) instrução(ões) associada(s) são executadas,
após o que o controle passa para a próxima instrução após o
END IF.
(Quaisquer condições IF subsequentes
não são testadas.)
Se nenhuma das condições IF resultar em verdade,
então será executado o bloco ELSE (se houver).
Aqui está um exemplo:
IF number = 0 THEN
result := 'zero';
ELSIF number > 0 THEN
result := 'positivo';
ELSIF number < 0 THEN
result := 'negativo';
ELSE
-- hmm, a única outra possibilidade é que o número seja nulo
result := 'NULL';
END IF;
A palavra-chave ELSIF também pode ser escrita
como ELSEIF.
Uma maneira alternativa de realizar a mesma tarefa é aninhar
instruções IF-THEN-ELSE, como no exemplo a seguir:
IF demo_row.sex = 'm' THEN
pretty_sex := 'man';
ELSE
IF demo_row.sex = 'f' THEN
pretty_sex := 'woman';
END IF;
END IF;
Entretanto, este método requer que seja escrito um
END IF correspondente para cada
IF, sendo muito mais complicado do que usar
ELSIF quando existem várias alternativas.
CASEexpressão_de_procuraWHENexpressão[,expressão[ ... ]] THENinstruções[ WHENexpressão[,expressão[ ... ]] THENinstruções... ] [ ELSEinstruções] END CASE;
A forma simples de CASE permite a execução
condicional segundo a igualdade de operandos.
A expressão_de_procura é avaliada
(uma vez), e comparada sucessivamente a cada
expressão nas cláusulas
WHEN.
Se for encontrada uma correspondência, então as
instruções correspondentes serão
executadas, e o controle passará para a próxima instrução após o
END CASE.
(As expressões WHEN subsequentes não são
avaliadas.)
Se nenhuma correspondência for encontrada, as
instruções do ELSE
serão executadas; mas se o ELSE não estiver
presente, então será lançada a exceção
CASE_NOT_FOUND.
Aqui está um exemplo simples:
CASE x
WHEN 1, 2 THEN
msg := 'um ou dois';
ELSE
msg := 'diferente de um e dois';
END CASE;
CASE
WHEN expressão_booleana THEN
instruções
[ WHEN expressão_booleana THEN
instruções
... ]
[ ELSE
instruções ]
END CASE;
A forma com pesquisa de CASE fornece execução
condicional baseada na verdade de expressões booleanas.
A expressão_booleana de cada cláusula
WHEN é avaliada sucessivamente, até que seja
encontrada uma que produza true.
Em seguida, as instruções correspondentes
são executadas, e o controle passa para a próxima instrução após
o END CASE.
(As expressões WHEN subsequentes não são avaliadas.)
Se não for encontrado nenhum resultado verdade, serão executadas as
instruções do ELSE;
mas se o ELSE não estiver presente,
será lançada uma exceção CASE_NOT_FOUND.
A seguir está um exemplo:
DO $$
DECLARE
x int :=21;
msg text;
BEGIN
CASE
WHEN x BETWEEN 0 AND 10 THEN
msg := 'o valor está entre zero e dez';
WHEN x BETWEEN 11 AND 20 THEN
msg := 'o valor está entre onze e vinte';
END CASE;
END;
$$;
ERROR: case não foi encontrado
HINT: comando CASE está faltando a parte ELSE.
CONTEXT: função PL/pgSQL inline_code_block linha 6 em CASE
Esta forma do CASE é totalmente equivalente a
IF-THEN-ELSIF, exceto pela regra onde atingir
uma cláusula ELSE omitida resulta em erro,
em vez de não fazer nada.
Com as instruções LOOP, EXIT,
CONTINUE, WHILE,
FOR e FOREACH, pode-se fazer
com que a função escrita em PL/pgSQL
repita uma série de comandos.
[ <<rótulo>> ] LOOPinstruçõesEND LOOP [rótulo];
LOOP define um laço incondicional,
repetido indefinidamente até ser terminado por uma instrução
EXIT ou RETURN.
O rótulo opcional pode ser usado pelas
instruções EXIT e CONTINUE
dentro de laços aninhados, para especificar a qual laço estas
instruções se referem.
EXIT [rótulo] [ WHENexpressão_booleana];
Se nenhum rótulo for fornecido, o
laço mais interno é terminado, e a instrução seguinte ao
END LOOP é executada em seguida.
Se rótulo for especificado, então
deve ser o rótulo do laço corrente, ou de algum nível externo de
laço ou bloco aninhado.
Em seguida, o laço ou bloco especificado é terminado, e o
controle continua com a instrução após o END
correspondente do laço/bloco.
Se for especificado WHEN, a saída do laço
ocorrerá somente se a expressão_booleana
resultar em verdade.
Caso contrário, o controle passará para a instrução após
EXIT.
EXIT pode ser usado com todos os tipos de
laço; não está limitado ao uso com laços incondicionais.
Quando usado em um bloco BEGIN,
EXIT passa o controle para a próxima
instrução após o final do bloco.
Note que deve ser usado um rótulo para esta finalidade;
um EXIT sem rótulo nunca é considerado
correspondendo a um bloco BEGIN.
(Esta é uma mudança com relação às versões do
PostgreSQL pré-8.4, que permitiam
que o EXIT sem rótulo correspondesse a um
bloco BEGIN.)
Exemplos:
LOOP
-- alguns cálculos
IF count > 0 THEN
EXIT; -- sair do laço
END IF;
END LOOP;
LOOP
-- alguns cálculos
EXIT WHEN count > 0; -- mesmo resultado do exemplo anterior
END LOOP;
<<obloco>>
BEGIN
-- alguns cálculos
IF estoque > 100000 THEN
EXIT obloco; -- causa a saída do bloco BEGIN
END IF;
-- os cálculos aqui são ignorados quando estoque > 100000
END;
CONTINUE [rótulo] [ WHENexpressão_booleana];
Se não for fornecido nenhum rótulo,
a próxima iteração do laço mais interno será iniciada.
Ou seja, todas as instruções restantes no corpo do laço são
ignoradas, e o controle retorna à expressão de controle do laço
(se houver) para determinar se é necessária outra iteração do laço.
Se rótulo estiver presente, especifica
o rótulo do laço cuja execução será continuada.
Se for especificado WHEN, a próxima iteração
do laço será iniciada somente se a
expressão_booleana resultar em verdade.
Caso contrário, o controle passará para a instrução após o
CONTINUE.
CONTINUE pode ser usado com todos os tipos
de laços; não está limitado ao uso com laços incondicionais.
Exemplos:
LOOP
-- alguns cálculos
EXIT WHEN contador > 100;
CONTINUE WHEN contador < 50;
-- alguns cálculos para contador IN [50 .. 100]
END LOOP;
[ <<rótulo>> ] WHILEexpressão_booleanaLOOPinstruçõesEND LOOP [rótulo];
A instrução WHILE repete uma sequência de
instruções enquanto a expressão_booleana
for avaliada como verdade.
A expressão é verificada antes de cada entrada no corpo do laço.
Por exemplo:
WHILE montante_devido > 0 AND saldo_vale_presente > 0 LOOP
-- alguns cálculos
END LOOP;
WHILE NOT terminado LOOP
-- alguns cálculos
END LOOP;
[ <<rótulo>> ] FORnomeIN [ REVERSE ]expressão..expressão[ BYexpressão] LOOPinstruçõesEND LOOP [rótulo];
Esta forma de FOR cria um laço que itera
sobre um intervalo de valores inteiros.
A variável nome é definida
automaticamente como sendo do tipo integer,
existindo apenas dentro do laço
(qualquer definição existente do nome da variável é ignorada
dentro do laço).
As duas expressões que fornecem o limite inferior e superior do
intervalo são avaliadas uma vez ao entrar no laço.
Se a cláusula BY não for especificada, o
passo da iteração é 1, caso contrário é o valor especificado
na cláusula BY, que novamente é avaliada
uma vez na entrada do laço.
Se for especificado REVERSE, o valor do passo
será subtraído, em vez de adicionado, após cada iteração.
Alguns exemplos de laços FOR inteiros:
FOR i IN 1..10 LOOP
-- i assume os valores 1,2,3,4,5,6,7,8,9,10 dentro do laço
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- i assume os valores 10,9,8,7,6,5,4,3,2,1 dentro do laço
END LOOP;
FOR i IN REVERSE 10..1 BY 2 LOOP
-- i assume os valores 10,8,6,4,2 dentro do laço
END LOOP;
Se o limite inferior for maior que o limite superior (ou menor
que, no caso REVERSE), o corpo do laço não
será executado, e nenhum erro será lançado.
Se for anexado um rótulo ao laço
FOR, então a variável inteira do laço
poderá ser referenciada com um nome qualificado, usando este
rótulo.
Usando um tipo diferente de laço FOR, é possível
iterar pelos resultados de uma consulta e manipular os dados de
acordo. A sintaxe é:
[ <<rótulo>> ] FORdestinoINconsultaLOOPinstruçõesEND LOOP [rótulo];
O destino é uma variável tipo-registro,
variável tipo-linha, ou uma lista separada por vírgulas de variáveis
escalares.
O destino é atribuído sucessivamente a
cada linha resultante da consulta,
e o corpo do laço é executado para cada linha de resultado.
Aqui está um exemplo:
CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
mviews RECORD;
BEGIN
RAISE NOTICE 'Atualização de todas as visões materializadas...';
FOR mviews IN
SELECT n.nspname AS mv_schema,
c.relname AS mv_name,
pg_catalog.pg_get_userbyid(c.relowner) AS owner
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
WHERE c.relkind = 'm'
ORDER BY 1
LOOP
-- Agora "mviews" tem um registro com informações sobre a visão materializada
RAISE NOTICE 'Atualizando a visão materializada %.% (dono: %)...',
quote_ident(mviews.mv_schema),
quote_ident(mviews.mv_name),
quote_ident(mviews.owner);
EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
END LOOP;
RAISE NOTICE 'Concluída a atualização das visualizações materializadas.';
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Se o laço for terminado pela instrução EXIT,
o último valor de linha atribuído ainda estará acessível após o laço.
A consulta usada neste tipo de instrução
FOR pode ser qualquer comando SQL
que retorne linhas para quem efetuou a chamada:
SELECT é o caso mais comum, mas também podem ser
usados INSERT, UPDATE,
DELETE ou MERGE com a
cláusula RETURNING.
Alguns comandos utilitários, como o EXPLAIN,
também funcionam.
As variáveis do PL/pgSQL são substituídas por parâmetros de consulta, e o plano de consulta é armazenado em cache para possível reutilização, conforme discutido em detalhes em Substituição de variável e Cache de plano.
A instrução FOR-IN-EXECUTE é outra maneira de
iterar sobre linhas:
[ <<rótulo>> ] FORdestinoIN EXECUTEtexto_da_expressão[ USINGexpressão[, ... ] ] LOOPinstruçõesEND LOOP [rótulo];
É como a forma anterior, exceto pela consulta de origem ser
especificada através de uma expressão de cadeia de caracteres
avaliada e replanejada a cada entrada no laço
FOR.
Isto permite que o programador escolha entre a velocidade de uma
consulta pré-planejada, ou a flexibilidade de uma consulta dinâmica,
assim como com uma simples instrução EXECUTE.
Como acontece com o EXECUTE, os valores
dos parâmetros podem ser inseridos no comando dinâmico através do
USING.
Outra maneira de especificar uma consulta cujos resultados devem ser iterados, é a declarar como um cursor. Isto é descrito em Laço através do resultado do cursor.
O laço FOREACH é muito parecido com o laço
FOR, mas em vez de iterar pelas linhas retornadas
por uma consulta SQL, itera pelos elementos
de um valor matriz.
(Em geral, FOREACH destina-se a percorrer os
componentes de uma expressão de valor composto; variantes para
percorrer tipos compostos, além de matrizes, poderão ser adicionadas
no futuro.)
A instrução FOREACH para percorrer uma matriz é:
[ <<rótulo>> ] FOREACHdestino[ SLICEnúmero] IN ARRAYexpressãoLOOPinstruçõesEND LOOP [rótulo];
Sem SLICE, ou se for especificado
SLICE 0, o laço itera pelos elementos
individuais da matriz produzidos pela avaliação da
expressão.
O valor de cada elemento é atribuído à variável
destino em sequência,
e o corpo do laço é executado para cada elemento.
Aqui está um exemplo de laço através dos elementos
de uma matriz de inteiros:
CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
s int8 := 0;
x int;
BEGIN
FOREACH x IN ARRAY $1
LOOP
s := s + x;
END LOOP;
RETURN s;
END;
$$ LANGUAGE plpgsql;
Os elementos são acessados na ordem do armazenamento,
independentemente do número de dimensões da matriz.
Embora o destino geralmente seja apenas
uma única variável, pode ser uma lista de variáveis ao percorrer
uma matriz de valores compostos (registros).
Neste caso, para cada elemento da matriz as variáveis são
atribuídas a partir de colunas sucessivas do valor composto.
Com um valor de SLICE positivo,
FOREACH itera através de fatias da matriz em
vez de elementos únicos.
O valor de SLICE deve ser uma constante inteira
não maior que o número de dimensões da matriz.
A variável destino deve ser uma matriz,
recebendo sucessivas fatias do valor da matriz, onde cada fatia
tem o número de dimensões especificado por SLICE.
Aqui está um exemplo de iteração através de fatias unidimensionais:
CREATE FUNCTION varre_linhas(int[]) RETURNS void AS $$
DECLARE
x int[];
BEGIN
FOREACH x SLICE 1 IN ARRAY $1
LOOP
RAISE NOTICE 'row = %', x;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT varre_linhas(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);
NOTA: row = {1,2,3}
NOTA: row = {4,5,6}
NOTA: row = {7,8,9}
NOTA: row = {10,11,12}
varre_linhas
--------------
(1 linha)
Por padrão, qualquer erro que ocorra em uma função
PL/pgSQL interrompe a execução da
função e da transação em volta dela.
Os erros podem ser capturados e tratados usando um bloco
BEGIN com a cláusula EXCEPTION.
A sintaxe é uma extensão da sintaxe normal para um bloco
BEGIN:
[ <<rótulo>> ] [ DECLAREdeclarações] BEGINinstruçõesEXCEPTION WHENcondição[ ORcondição... ] THENinstruções_do_tratador[ WHENcondição[ ORcondição... ] THENinstruções_do_tratador... ] END;
Se não ocorrer nenhum erro, esta forma de bloco simplesmente executa
todas as instruções, e então o controle
passa para a próxima instrução após o END.
Mas se ocorrer um erro nas instruções,
o processamento das instruções posteriores
é abandonado, e o controle passa para a lista EXCEPTION.
A lista é procurada pela primeira condição
que corresponder ao erro ocorrido.
Se for encontrada uma correspondência, as
instruções_do_tratador correspondentes
serão executadas, e o controle passará para a próxima instrução
após o END.
Se não for encontrada nenhuma correspondência, o erro se propagará
como se a cláusula EXCEPTION não existisse:
o erro pode ser capturado por um bloco envoltório contendo
EXCEPTION, mas caso não haja nenhum, o erro
interromperá o processamento da função.
Os nomes das condições podem ser qualquer
um dos mostrados em Códigos de erro do PostgreSQL.
Um nome de categoria corresponde a qualquer erro dentro de sua categoria.
O nome de condição especial OTHERS corresponde
a todos os tipos de erro, exceto QUERY_CANCELED
e ASSERT_FAILURE.
(É possível, mas muitas vezes imprudente, interceptar estes dois
tipos de erro pelo nome.)
Os nomes das condições não diferenciam letras maiúsculas de minúsculas.
Além disso, uma condição de erro pode ser especificada pelo código
SQLSTATE; por exemplo, estes dois fragmentos
são equivalentes:
WHEN division_by_zero THEN ... WHEN SQLSTATE '22012' THEN ...
Se ocorrer um novo erro dentro das
instruções_do_tratador selecionadas,
este erro não poderá ser capturado por esta cláusula
EXCEPTION, mas será propagado.
Uma cláusula EXCEPTION envoltória poderá 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
alterações no estado persistente do banco de dados dentro o bloco
é desfeito.
Como exemplo, considere este fragmento:
INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
UPDATE mytab SET firstname = 'Joe' WHERE lastname = '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 atingir a atribuição para y,
falhará com um erro de division_by_zero.
Isto será capturado pela cláusula EXCEPTION.
O valor retornado na instrução RETURN será o
valor incrementado de x, mas os efeitos do
comando UPDATE serão desfeitos.
O comando INSERT que precede o bloco não é
desfeito, portanto, o resultado final é que o banco de dados contém
Tom Jones, e não Joe Jones.
Um bloco contendo uma cláusula EXCEPTION
é significativamente mais caro para entrar e sair do que um
bloco sem ela.
Portanto, não deve ser usado EXCEPTION
sem necessidade.
Exemplo 41.4. Exceções com UPDATE/INSERT
Este exemplo usa o tratamento de exceção para executar
UPDATE ou INSERT,
conforme apropriado.
Recomenda-se que as aplicações usem INSERT com
ON CONFLICT DO UPDATE, em vez de usar
este método.
Este exemplo serve, principalmente, para ilustrar o uso de estruturas
de fluxo de controle do PL/pgSQL:
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
-- primeiro vamos tentar atualizar a chave
UPDATE db SET b = data WHERE a = key;
IF found THEN
RETURN;
END IF;
-- se não existir, então vamos tentar inserir a chave
-- se outra pessoa inserir a mesma chave ao mesmo tempo,
-- então pode acontecer uma falha de chave única
BEGIN
INSERT INTO db(a,b) VALUES (key, data);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- Não faça nada, e continue o laço
-- para tentar o UPDATE novamente.
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
Este código assume que o erro unique_violation
é causado pelo INSERT, e não por, digamos, um
INSERT em uma função de gatilho na tabela.
O código também pode se comportar mal quando há mais de um índice
único na tabela, porque repetirá a operação independentemente de
qual índice causou o erro.
Pode ser obtida uma segurança maior usando os recursos discutidos
a seguir para verificar se o erro capturado era o esperado.
Os tratadores de exceção frequentemente precisam identificar o
erro específico ocorrido.
Existem duas maneiras de obter informações sobre a exceção corrente
no PL/pgSQL: variáveis especiais,
e o comando GET STACKED DIAGNOSTICS.
Dentro de um tratador de exceção, a variável especial
SQLSTATE contém o código de erro que corresponde
à exceção que foi levantada
(veja a Tabela de códigos de erro do PostgreSQL para obter a lista de
possíveis códigos de erro).
A variável especial SQLERRM contém a mensagem
de erro associada à exceção.
Estas variáveis não estão definidas fora dos tratadores de exceção.
Dentro de um tratador de exceção, também é possível obter
informações sobre a exceção corrente usando o comando
GET STACKED DIAGNOSTICS, que tem a forma:
GET STACKED DIAGNOSTICSvariável{ = | := }item[ , ... ];
Cada item é uma palavra-chave que
identifica um valor de status a ser atribuído à
variável especificada
(que deve ser do tipo de dados correto para recebê-lo).
Os itens de status atualmente disponíveis estão mostrados na
Tabela 41.2.
Tabela 41.2. Itens de diagnóstico de erro
| Nome | Tipo | Descrição |
|---|---|---|
RETURNED_SQLSTATE | text | o código de erro SQLSTATE da exceção |
COLUMN_NAME | text | o nome da coluna relacionada à exceção |
CONSTRAINT_NAME | text | o nome da restrição relacionada à exceção |
PG_DATATYPE_NAME | text | o nome do tipo de dados relacionado à exceção |
MESSAGE_TEXT | text | o texto da mensagem principal da exceção |
TABLE_NAME | text | o nome da tabela relacionada à exceção |
SCHEMA_NAME | text | o nome do esquema relacionado à exceção |
PG_EXCEPTION_DETAIL | text | o texto da mensagem de detalhes da exceção, se houver |
PG_EXCEPTION_HINT | text | o texto da mensagem de dica da exceção, se houver |
PG_EXCEPTION_CONTEXT | text | linha(s) de texto descrevendo a pilha de chamadas no momento da exceção (veja Obtenção de informações sobre o local da execução) |
Se a exceção não definir um valor para um item, será retornada uma cadeia de caracteres vazia.
Aqui está um exemplo:
DECLARE
text_var1 text;
text_var2 text;
text_var3 text;
BEGIN
-- algum processamento que pode causar uma exceção
...
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
END;
O comando GET DIAGNOSTICS, descrito anteriormente
em Obtenção do status do resultado, recupera
informações sobre o estado da execução corrente
(enquanto o comando GET STACKED DIAGNOSTICS,
discutido acima, mostra informações sobre o estado da execução
de um erro anterior).
Seu item de status PG_CONTEXT é útil para
identificar o local corrente da execução.
PG_CONTEXT retorna uma cadeia de caracteres
com linha(s) de texto descrevendo a pilha de chamadas.
A primeira linha refere-se à função corrente e ao comando
GET DIAGNOSTICS atualmente em execução.
A segunda, e quaisquer linhas subsequentes, referem-se a funções
de chamada mais acima na pilha de chamadas. Por exemplo:
CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$ BEGIN RETURN inner_func(); END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$ DECLARE stack text; BEGIN GET DIAGNOSTICS stack = PG_CONTEXT; RAISE NOTICE E'--- Call Stack ---\n%', stack; RETURN 1; END; $$ LANGUAGE plpgsql; SELECT outer_func();
NOTA: --- Call Stack ---
função PL/pgSQL inner_func() linha 5 em GET DIAGNOSTICS
função PL/pgSQL outer_func() linha 3 em RETURN
outer_func
------------
1
(1 linha)
GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT
retorna o mesmo tipo de rastreamento de pilha, mas descrevendo o
local em que o erro foi detectado, em vez do local corrente.