As funções SQL executam uma lista arbitrária de
instruções SQL, retornando o resultado da última
consulta numa lista.
No caso mais simples (não conjunto), será retornada a primeira
linha do resultado da última consulta.
(Tenha em mente que a “primeira linha” de um resultado
de várias linhas não fica bem definido, a menos que seja usado
ORDER BY.)
Se a última consulta não retornar nenhuma linha, será retornado
o valor nulo.
Como alternativa, a função SQL pode ser declarada
retornando um conjunto (ou seja, várias linhas), especificando o
tipo de dados retornado pela função como
SETOF ,
ou de forma equivalente, declarando a função com
algum_tipoRETURNS TABLE(.
Nesses casos, são retornadas todas as linhas do resultado da última
consulta. Abaixo são mostrados mais detalhes.
colunas)
O corpo de uma função SQL deve ser uma lista de
instruções SQL separadas por ponto e vírgula.
O ponto e vírgula após a última instrução é opcional.
A menos que a função seja declarada retornando void,
a última declaração deve ser um SELECT, ou um
INSERT, UPDATE,
DELETE ou MERGE
contendo a cláusula RETURNING.
Qualquer coleção de comandos na linguagem SQL
pode ser agrupada e definida como uma função.
Além das consultas SELECT, os comandos podem
incluir instruções de modificação de dados (INSERT,
UPDATE, DELETE e
MERGE), assim como outros comandos
SQL.
(Não podem ser usados comandos de controle de transação, por exemplo,
COMMIT, SAVEPOINT,
e alguns comandos utilitários, por exemplo, VACUUM,
nas funções SQL.)
Entretanto, o comando final deve ser um SELECT,
ou ter uma cláusula RETURNING retornando o que
foi especificado como o tipo de dados retornado pela função.
Como alternativa, se for desejado definir uma função
SQL que execute ações, mas não tenha nenhum
valor útil a ser retornado, esta função pode ser definida como
retornando void.
Por exemplo, a seguinte função remove as linhas com salários
negativos da tabela emp:
CREATE FUNCTION clean_emp() RETURNS void AS '
DELETE FROM emp
WHERE salary < 0;
' LANGUAGE SQL;
SELECT clean_emp();
clean_emp ----------- (1 linha)
Como alternativa, a função poderia ser escrita como um procedimento, evitando assim o problema do tipo de dados retornado. Por exemplo:
CREATE PROCEDURE clean_emp() AS '
DELETE FROM emp
WHERE salary < 0;
' LANGUAGE SQL;
CALL clean_emp();
Em casos simples como este, a diferença entre uma função retornando
void e um procedimento, é principalmente de estilo.
Entretanto, os procedimentos oferecem funcionalidades adicionais,
como controle de transações, que não estão disponíveis nas funções.
Além disso, os procedimentos fazem parte do padrão
SQL, enquanto retornar void é uma
extensão do PostgreSQL.
A sintaxe do comando CREATE FUNCTION requer que o
corpo da função seja escrito como uma constante cadeia de caracteres.
Geralmente, é mais conveniente usar a delimitação por cifrão
(veja Constantes do tipo cadeia de caracteres delimitadas por cifrão) para a
constante cadeia de caracteres.
Se for usada a sintaxe de constante cadeia de caracteres entre
apóstrofos, os apóstrofos (') e contrabarras
(\) no corpo da função deverão ser dobrados
(assumindo que esteja sendo usada a sintaxe de escape na cadeia de
caracteres)
(veja Constantes do tipo cadeia de caracteres).
Os argumentos de uma função SQL podem ser referenciados no corpo da função usando seus nomes ou números. Exemplos dos dois métodos são mostrados abaixo.
Para usar nome, o argumento da função deve ser declarado como
tendo um nome e, em seguida, este nome deve ser escrito no corpo
da função.
Se o nome do argumento for igual a qualquer nome de coluna no
comando SQL atual da função, o nome da coluna
terá precedência.
Para mudar este comportamento, o nome do argumento deve ser
qualificado com o nome da própria função, ficando
.
(Se esta qualificação entrar em conflito com um nome de coluna
qualificado, novamente o nome da coluna terá precedência.
A ambiguidade pode ser evitada escolhendo um alias diferente para
a tabela dentro do comando SQL.)
nome_da_função.nome_do_argumento
Na abordagem numérica mais antiga, os argumentos são referenciados
usando a sintaxe $:
n$1 refere-se ao primeiro argumento de entrada,
$2 ao segundo, e assim por diante.
Isto funciona independentemente do argumento ter sido
declarado com nome, ou não.
Se o argumento for de tipo de dados composto, então a notação
de ponto, por exemplo,
,
ou nome_do_argumento.nome_do_campo$1.,
poderá ser usada para acessar os atributos do argumento.
Novamente, pode ser necessário qualificar o nome do argumento com
o nome da função, para tornar a forma com nome do argumento inequívoca.
nome_do_campo
Os argumentos da função SQL só podem ser usados como valores de dados, e não como identificadores. Assim, por exemplo, este comando é possível:
INSERT INTO minha_tabela VALUES ($1);
mas este não vai funcionar:
INSERT INTO $1 VALUES (42);
A capacidade de usar nomes para referenciar argumentos de funções
SQL foi adicionada no
PostgreSQL 9.2.
As funções a serem usadas em servidores mais antigos devem usar
a notação $.
n
A função SQL mais simples possível não possui
argumentos, e apenas retorna um tipo de dados base, como
integer:
CREATE FUNCTION um() RETURNS integer AS $$
SELECT 1 AS resultado;
$$ LANGUAGE SQL;
-- Sintaxe alternativa usando apóstrofos:
CREATE FUNCTION um() RETURNS integer AS '
SELECT 1 AS resultado;
' LANGUAGE SQL;
SELECT um();
um ---- 1 (1 linha)
Deve ser observado que, dentro do corpo da função acima,
foi definido um alias de coluna para o resultado da função
(com o nome resultado),
mas este alias de coluna não é visível fora da função.
Por isto, o resultado tem o rótulo um,
em vez de resultado.
Também é fácil definir funções SQL que usam argumentos com tipos de dados base:
CREATE FUNCTION soma_num(x integer, y integer) RETURNS integer AS $$
SELECT x + y;
$$ LANGUAGE SQL;
SELECT soma_num(1, 2) AS soma;
soma
------
3
(1 linha)
Como alternativa, os nomes dos argumentos poderiam ser dispensados e usados os números de suas posições:
CREATE FUNCTION soma_num(integer, integer) RETURNS integer AS $$
SELECT $1 + $2;
$$ LANGUAGE SQL;
SELECT soma_num(1, 2) AS soma;
soma
------
3
(1 linha)
A seguir está uma função mais útil, que pode ser usada para realizar débito em uma conta bancária:
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
UPDATE bank
SET balance = balance - debit
WHERE accountno = tf1.accountno;
SELECT 1;
$$ LANGUAGE SQL;
Um usuário pode executar esta função para debitar $100,00 da conta 17 da seguinte forma:
SELECT tf1(17, 100.0);
Neste exemplo, foi escolhido o nome accountno
para o primeiro argumento, mas é idêntico ao nome de uma coluna
na tabela bank.
Dentro do comando UPDATE,
accountno refere-se à coluna
bank.accountno, portanto deve ser usado
tf1.accountno para se referir ao argumento.
É claro que isto pode ser evitado usando um nome diferente
para o argumento.
Na prática, provavelmente se gostaria de obter um resultado mais
útil da função do que a constante 1, portanto,
uma definição mais razoável seria
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
UPDATE bank
SET balance = balance - debit
WHERE accountno = tf1.accountno;
SELECT balance FROM bank WHERE accountno = tf1.accountno;
$$ LANGUAGE SQL;
que ajusta o saldo e retorna o novo saldo.
O mesmo resultado pode ser obtido usando a cláusula
RETURNING:
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
UPDATE bank
SET balance = balance - debit
WHERE accountno = tf1.accountno
RETURNING balance;
$$ LANGUAGE SQL;
Se a cláusula final SELECT ou
RETURNING em uma função SQL
não retornar um resultado do mesmo tipo de dados declarado na
função, o PostgreSQL converterá
automaticamente o valor para o tipo de dados declarado, se isto
for possível com uma conversão implícita ou de atribuição.
Caso contrário, deve ser escrita uma conversão explícita.
Por exemplo, se for desejado que a função
soma_num anterior retorne o tipo de dados
float8, bastará então escrever
CREATE FUNCTION soma_num(integer, integer) RETURNS float8 AS $$
SELECT $1 + $2;
$$ LANGUAGE SQL;
já que a soma do tipo de dados integer pode ser
convertida implicitamente para float8.
(Veja Conversão de tipo de dados ou CREATE CAST
para obter mais informações sobre conversões.)
Ao escrever funções com argumentos de tipos de dados compostos,
não se deve apenas especificar qual argumento se deseja, mas
também o atributo desejado (campo) desse argumento.
Por exemplo, supondo que emp seja uma tabela contendo
dados de funcionários, portanto, será também o nome do tipo de dados
composto de cada linha da tabela.
A seguir está mostrada a função dobrar_salário,
que calcula qual seria o salário de alguém, se este fosse dobrado:
CREATE TABLE emp (
nome text,
salário numeric,
idade integer,
baia point
);
INSERT INTO emp VALUES ('Bill', 4200, 45, '(2,1)');
CREATE FUNCTION dobrar_salário(emp) RETURNS numeric AS $$
SELECT $1.salário * 2 AS salário;
$$ LANGUAGE SQL;
SELECT nome, dobrar_salário(emp.*) AS sonho
FROM emp
WHERE emp.baia ~= point '(2,1)';
nome | sonho ------+------- Bill | 8400 (1 linha)
Deve ser observado o uso da sintaxe $1.salário
para selecionar o campo salário da linha do argumento.
Veja, também, que o comando SELECT chamando
a função usa nome_da_tabela.*
para selecionar toda a linha corrente da tabela como valor composto.
A linha da tabela também pode ser referenciada usando apenas o
nome da tabela, da seguinte forma
SELECT nome, dobrar_salário(emp) AS sonho
FROM emp
WHERE emp.baia ~= point '(2,1)';
mas este uso está em obsolescência (deprecated), porque é fácil causar confusão. (Veja Uso de tipos de dados compostos em consultas para obter detalhes sobre estas duas notações para o valor composto de uma linha da tabela.)
Às vezes, é útil construir o valor do argumento composto em tempo
de execução (on the fly).
Isto pode ser feito com a construção ROW.
Por exemplo, pode-se ajustar os dados sendo passados
para a função:
SELECT nome, dobrar_salário(ROW(nome, salário*1.1, idade, baia)) AS sonho
FROM emp;
Também é possível construir uma função retornando um tipo de dados
composto.
A seguir está um exemplo de função que retorna uma única linha
do tipo de dados emp:
CREATE FUNCTION novo_empregado() RETURNS emp AS $$
SELECT text 'Ninguém' AS nome,
1000.0 AS salário,
25 AS idade,
point '(2,2)' AS baia;
$$ LANGUAGE SQL;
Neste exemplo, cada um dos atributos é especificado usando um valor constante, mas estas constantes podem ser substituídas por algum cálculo.
Devem ser observadas duas coisas importantes sobre a definição da função:
A ordem da lista de seleção na consulta deve ser idêntica à ordem em que as colunas aparecem no tipo de dados composto. (Dar nomes às colunas, como foi feito acima, é irrelevante para o sistema.)
Deve-se garantir que o tipo de dados de cada expressão possa ser convertido para o tipo de dados da coluna correspondente do tipo de dados composto. Caso contrário, vão ser mostrados erros como este:
ERRO: O tipo de dados retornado não corresponde ao que foi declarado na função emp
DETALHE: O último comando retornou text ao invés de point na coluna 4.
CONTEXTO: função SQL "novo_empregado"
Assim como no caso do tipo de dados base, o sistema não insere conversões explícitas automaticamente, realizando apenas conversões implícitas ou de atribuição.
Uma forma diferente de definir a mesma função é:
CREATE FUNCTION novo_empregado() RETURNS emp AS $$
SELECT ROW('Ninguém', 1000.0, 25, '(2,2)')::emp;
$$ LANGUAGE SQL;
Aqui foi escrito um comando SELECT retornando
apenas uma única coluna do tipo de dados composto correto.
Na verdade, isto não é melhor nesta situação, mas é uma alternativa
útil em alguns casos — por exemplo, se for necessário calcular
o resultado chamando outra função que retorne o valor composto desejado.
Outro exemplo, é que se estivermos tentando escrever uma função que
retorne um domínio sobre um tipo de dados composto, em vez de um
tipo de dados composto simples, é sempre necessário escrevê-la como
retornando uma única coluna, por não haver como fazer uma conversão
de resultado da linha inteira.
Podemos chamar esta função diretamente, usando-a em uma expressão de valor:
SELECT novo_empregado();
novo_empregado
-----------------------------
(Ninguém,1000.0,25,"(2,2)")
(1 linha)
ou chamando-a como uma função de tabela:
SELECT * FROM novo_empregado();
nome | salário | idade | baia ---------+---------+-------+------- Ninguém | 1000.0 | 25 | (2,2) (1 linha)
A segunda forma é descrita em mais detalhes em Funções SQL como fonte de tabela.
Ao usar uma função retornando tipo de dados composto, pode-se desejar apenas um campo (atributo) do resultado. Isto pode ser feito com uma sintaxe como esta:
SELECT (novo_empregado()).nome;
nome --------- Ninguém (1 linha)
Os parênteses extras são necessários para evitar que o analisador se confunda. Se for tentado fazer isto sem os parênteses extras, será retornado algo assim:
SELECT novo_empregado().nome;
ERRO: erro de sintaxe em ou próximo a "."
LINHA 1: SELECT novo_empregado().nome;
^
Outra opção é usar a notação de função para extrair um atributo:
SELECT nome(novo_empregado());
nome --------- Ninguém (1 linha)
Conforme explicado em Uso de tipos de dados compostos em consultas, a notação de campo e a notação de função são equivalentes.
Outra forma de usar uma função retornando tipo de dados composto, é passar o resultado para outra função que aceite o tipo de dados de linha correto como entrada:
CREATE FUNCTION traz_nome(emp) RETURNS text AS $$
SELECT $1.nome;
$$ LANGUAGE SQL;
SELECT traz_nome(novo_empregado());
traz_nome ----------- Ninguém (1 linha)
Uma forma alternativa de descrever os resultados de uma função é defini-la com parâmetros de saída, como neste exemplo:
CREATE FUNCTION soma_int (IN x int, IN y int, OUT sum int) AS 'SELECT x + y' LANGUAGE SQL; SELECT soma_int(3,7);
soma_int
----------
10
(1 linha)
Esta função não é essencialmente diferente da versão de
soma_int mostrada em
Funções SQL em tipos de dados base.
A verdadeira vantagem dos parâmetros de saída, é o fato de fornecerem
uma forma conveniente de definir funções que retornam várias colunas.
Por exemplo:
CREATE FUNCTION soma_prod_int (x int, y int, OUT soma int, OUT produto int) AS 'SELECT x + y, x * y' LANGUAGE SQL; SELECT * FROM soma_prod_int(11, 42);
soma | produto ------+--------- 53 | 462 (1 linha)
O que basicamente aconteceu nesta função, é que foi criado um tipo de dados composto anônimo para o resultado da função. O exemplo acima produz o mesmo resultado que
CREATE TYPE soma_prod AS (soma int, produto int); CREATE FUNCTION soma_prod_int (int, int) RETURNS soma_prod AS 'SELECT $1 + $2, $1 * $2' LANGUAGE SQL; SELECT * FROM soma_prod_int(11, 42);
soma | produto ------+--------- 53 | 462 (1 linha)
porém, não haver necessidade de se preocupar com uma definição em separado do tipo de dados composto, é geralmente útil. Veja que os nomes anexados aos parâmetros de saída não são apenas decorativos, eles determinam os nomes das colunas do tipo de dados composto anônimo. (Se for omitido o nome de um parâmetro de saída, o sistema vai escolher um nome por conta própria.)
Deve-se observar que os parâmetros de saída não são incluídos na lista de argumentos de chamada, ao invocar uma função SQL. Isto acontece, porque o PostgreSQL considera apenas os parâmetros de entrada para definir a assinatura de chamada da função. Isto significa, também, que apenas os parâmetros de entrada importam ao referenciar a função para finalidades como eliminá-la. A função acima poderia ser excluída com qualquer um desses dois comandos:
DROP FUNCTION soma_prod_int (x int, y int, OUT sum int, OUT product int); DROP FUNCTION sum_n_product (int, int);
Os parâmetros podem ser marcados como IN
(o padrão), OUT, INOUT ou
VARIADIC.
Um parâmetro INOUT serve como parâmetro de
entrada (parte da lista de argumentos de chamada), e parâmetro de
saída (parte do tipo de dados registro do resultado).
Os parâmetros VARIADIC são parâmetros de entrada,
mas são tratados de forma especial conforme descrito mais abaixo.
Também há suporte para parâmetros de saída em procedimentos,
mas funcionam de forma um pouco diferente das funções.
Nos comandos CALL, os parâmetros de saída
devem ser incluídos na lista de argumentos.
Por exemplo, a rotina de débito em conta-corrente anterior poderia
ser escrita como:
CREATE PROCEDURE tp1 (accountno integer, debit numeric, OUT new_balance numeric) AS $$
UPDATE bank
SET balance = balance - debit
WHERE accountno = tp1.accountno
RETURNING balance;
$$ LANGUAGE SQL;
Para chamar este procedimento, deve ser incluído um argumento
correspondente ao parâmetro OUT.
É costume escrever NULL:
CALL tp1(17, 100.0, NULL);
Se for escrito alguma outra coisa, deve ser uma expressão que possa ser convertida implicitamente para o tipo de dados declarado do parâmetro, assim como para os parâmetros de entrada. No entanto, deve-se observar que esta expressão não será avaliada.
Ao chamar um procedimento a partir do
PL/pgSQL, ao invés de escrever
NULL deve ser escrito o nome da variável
que vai receber a saída do procedimento.
Veja Chamada de procedimento
para obter mais detalhes.
As funções SQL podem ser declaradas aceitando
um número variável de argumentos, desde que todos os argumentos
“opcionais” sejam do mesmo tipo de dados.
Os argumentos opcionais são passados para a função como uma matriz.
A função é declarada marcando o último parâmetro como
VARIADIC; este parâmetro deve ser declarado
como sendo do tipo de dados matriz. Por exemplo:
CREATE FUNCTION menor(VARIADIC arr numeric[]) RETURNS numeric AS $$
SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;
SELECT menor(10, -1, 5, 4.4);
menor
-------
-1
(1 linha)
De fato, todos os argumentos passados na posição
VARIADIC, ou além, são reunidos em uma matriz
unidimensional, como se tivesse sido escrito:
SELECT menor(ARRAY[10, -1, 5, 4.4]); -- não funciona
Porém, não pode ser escrito assim — ou, pelo menos, não vai
corresponder a esta definição de função.
Um parâmetro marcado como VARIADIC corresponde
a uma ou mais ocorrências de seu tipo de dados de elemento,
não de seu próprio tipo de dados.
Às vezes é útil poder passar uma matriz já construída para uma
função variádica; isto é particularmente útil quando a função
variádica deseja passar seu parâmetro de matriz para outra.
Além disso, esta é a única forma segura de chamar uma função
variádica que se encontra em um esquema que permite que usuários
não confiáveis criem objetos; veja Funções.
Isto pode ser feito especificando VARIADIC na
chamada:
SELECT menor(VARIADIC ARRAY[10, -1, 5, 4.4]);
Assim se evita a expansão do parâmetro variável da função em seu
tipo de dados de elemento, permitindo que o valor do argumento da
matriz corresponda normalmente.
VARIADIC só pode ser anexado ao último argumento
real de uma chamada de função.
Especificar VARIADIC na chamada também é a única
forma de passar uma matriz vazia para uma função variádica.
Por exemplo:
\pset null "<null>" A exibição de nulos é ""<null>"". SELECT menor(VARIADIC ARRAY[]::numeric[]); menor ---------- "<null>" (1 linha)
Escrever apenas SELECT menor() não
funciona, porque o parâmetro variádico deve corresponder a pelo
menos um argumento real.
(Pode ser definida uma segunda função, também chamada
menor, sem parâmetros, se for desejado permitir
esta forma de chamada.)
Os parâmetros do elemento matriz gerados a partir de um parâmetro
variádico são tratados como não tendo nomes próprios,
significando não ser possível chamar uma função variádica
usando argumentos com nomes
(veja Chamadas de função),
exceto quando for especificado VARIADIC.
Por exemplo, este comando funciona:
SELECT menor(VARIADIC arr => ARRAY[10, -1, 5, 4.4]);
mas estes não:
SELECT menor(arr => 10); SELECT menor(arr => ARRAY[10, -1, 5, 4.4]);
As funções podem ser declaradas com valor padrão para alguns ou todos os argumentos de entrada. O valor padrão será inserido sempre que a função for chamada com argumentos reais insuficientes. Como os argumentos só podem ser omitidos do final da lista de argumentos, todos os parâmetros após um parâmetro com valor padrão também devem ter valor padrão. (Embora o uso da notação de argumento com nome possa permitir que esta restrição seja afrouxada, ela ainda é aplicada para que a notação de argumento posicional funcione de maneira consistente.) Independentemente de ser usado ou não, este recurso cria a necessidade de se tomar precauções ao chamar funções em bancos de dados onde alguns usuários não confiam em outros usuários; veja Funções.
Por exemplo:
CREATE FUNCTION foo(a int, b int DEFAULT 2, c int DEFAULT 3)
RETURNS int
LANGUAGE SQL
AS $$
SELECT $1 + $2 + $3;
$$;
SELECT foo(10, 20, 30);
foo
-----
60
(1 linha)
SELECT foo(10, 20);
foo
-----
33
(1 linha)
SELECT foo(10);
foo
-----
15
(1 linha)
SELECT foo(); -- falha, por não existir valor padrão para o primeiro argumento
ERRO: função foo() não existe
LINHA 1: SELECT foo();
^
DICA: Nenhuma função corresponde com o nome e os tipos de argumentos informados.
Você precisa adicionar conversões de tipo explícitas.
O sinal = também pode ser usado no lugar da
palavra-chave DEFAULT.
Todas as funções SQL podem ser usadas na cláusula
FROM de uma consulta, mas é particularmente útil
para funções que retornam tipos de dados compostos.
Se a função for definida retornando um tipo de dados base,
a função de tabela produzirá uma tabela de uma coluna.
Se a função for definida retornando um tipo de dados composto,
a função de tabela produzirá uma coluna para cada atributo do
tipo de dados composto.
A seguir está um exemplo:
CREATE TABLE foo (fooid int, foosubid int, fooname text);
INSERT INTO foo VALUES (1, 1, 'Joe');
INSERT INTO foo VALUES (1, 2, 'Ed');
INSERT INTO foo VALUES (2, 1, 'Mary');
CREATE FUNCTION getfoo(int) RETURNS foo AS $$
SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;
SELECT *, upper(fooname) FROM getfoo(1) AS t1;
fooid | foosubid | fooname | upper
-------+----------+---------+-------
1 | 1 | Joe | JOE
(1 linha)
Como este exemplo mostra, pode-se trabalhar com as colunas do resultado da função da mesma forma como se fossem colunas de uma tabela normal.
Deve-se observar que a função retornou apenas uma linha.
Isto se deve a não ter sido usado SETOF,
como está descrito na próxima seção.
Quando uma função SQL é declarada retornando
SETOF ,
a consulta final da função é executada até concluir, e cada
linha gerada é retornada como um elemento do conjunto de
resultados.
algum_tipo
Este recurso é normalmente usado ao chamar a função na cláusula
FROM.
Neste caso, cada linha retornada pela função torna-se uma linha
da tabela vista pela consulta.
Por exemplo, supondo que a tabela foo tenha
o mesmo conteúdo acima, e seja escrito
CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$
SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
será obtido:
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
1 | 2 | Ed
(2 linhas)
Também é possível retornar várias linhas com as colunas definidas pelos parâmetros de saída, dessa forma:
CREATE TABLE tab (y int, z int);
INSERT INTO tab VALUES (1, 2), (3, 4), (5, 6), (7, 8);
CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int)
RETURNS SETOF record
AS $$
SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
SELECT * FROM sum_n_product_with_tab(10);
sum | product -----+--------- 11 | 10 13 | 30 15 | 50 17 | 70 (4 linhas)
O ponto chave aqui, é que deve ser escrito
RETURNS SETOF record para indicar que a função
retorna várias linhas, em vez de apenas uma linha.
Havendo apenas um parâmetro de saída, deve ser escrito o tipo de dados
desse parâmetro, em vez de record.
Frequentemente, é útil construir o resultado de uma consulta
chamando várias vezes uma função que retorna conjunto, com os
parâmetros para cada chamada provenientes de linhas sucessivas
de uma tabela ou sub-consulta.
A forma preferida de fazer isto é usando a palavra-chave
LATERAL, descrita em
Subconsultas com LATERAL.
A seguir está um exemplo usando uma função retornando conjunto
para enumerar elementos de uma estrutura de árvore:
SELECT * FROM nodes;
name | parent -----------+-------- Top | Child1 | Top Child2 | Top Child3 | Top SubChild1 | Child1 SubChild2 | Child1 (6 linhas)
CREATE FUNCTION listchildren(text) RETURNS SETOF text AS $$
SELECT name FROM nodes WHERE parent = $1
$$ LANGUAGE SQL STABLE;
SELECT * FROM listchildren('Top');
listchildren -------------- Child1 Child2 Child3 (3 linhas)
SELECT name, child FROM nodes, LATERAL listchildren(name) AS child;
name | child --------+----------- Top | Child1 Top | Child2 Top | Child3 Child1 | SubChild1 Child1 | SubChild2 (5 linhas)
Este exemplo não faz nada que não poderia ser feito com uma simples junção, mas em cálculos mais complexos, a opção de colocar parte do trabalho em uma função pode ser bastante conveniente.
As funções retornando conjuntos também podem ser chamadas na lista de seleção de uma consulta. Para cada linha que a consulta gera sozinha, a função retornando conjunto é chamada, e uma linha de saída é gerada para cada elemento do conjunto de resultados da função. O exemplo anterior também poderia ser feito com consultas como estas:
SELECT listchildren('Top');
listchildren -------------- Child1 Child2 Child3 (3 linhas)
SELECT name, listchildren(name) FROM nodes;
name | listchildren --------+-------------- Top | Child1 Top | Child2 Top | Child3 Child1 | SubChild1 Child1 | SubChild2 (5 linhas)
Deve-se observar que, no último SELECT não
aparece nenhuma linha de saída para Child2,
Child3, etc.
Isto se dá, porque listchildren retorna um
conjunto vazio para estes argumentos, portanto, nenhuma linha de
resultado é gerada.
Este é o mesmo comportamento obtido em uma junção interna para o
resultado da função ao usar a sintaxe LATERAL.
O comportamento do PostgreSQL para uma
função retornando conjunto na lista de seleção da consulta,
é quase o mesmo que se a função retornando conjunto tivesse sido
escrita em um item da cláusula LATERAL FROM.
Por exemplo,
SELECT x, generate_series(1,5) AS g FROM tab;
é quase equivalente a
SELECT x, g FROM tab, LATERAL generate_series(1,5) AS g;
Seria exatamente o mesmo, exceto que neste exemplo específico,
o planejador poderia escolher colocar g
fora da junção de laço aninhado, já que g
não tem dependência lateral na tabela tab.
Isto resultaria em uma ordem diferente das linhas de saída.
As funções retornando conjunto na lista de seleção são sempre
avaliadas como se estivessem dentro da junção de laço aninhado
com o restante da cláusula FROM, para que
a(s) função(ões) seja(m) executada(s) até concluir(em), antes que a
próxima linha da cláusula FROM seja considerada.
Havendo mais de uma função retornando conjunto na lista de seleção
da consulta, o comportamento será semelhante ao obtido colocando as
funções em um único item de cláusula.
LATERAL ROWS FROM( ... ) FROM.
Para cada linha da consulta subjacente, há uma linha de saída usando
o primeiro resultado de cada função, depois uma linha de saída usando
o segundo resultado, e assim por diante.
Se algumas das funções retornando conjunto produzirem menos saídas
do que outras, os dados ausentes serão substituídos por valores nulos,
de modo que o número total de linhas emitidas para uma linha subjacente
seja o mesmo da função retornando conjunto que produziu a maioria
das saídas.
Assim, as funções retornando conjunto são executadas
“em pari passu”
(lockstep),
até que todas sejam esgotadas e, em seguida,
a execução continua com a próxima linha subjacente.
As funções retornando conjunto podem ser aninhadas em uma lista de
seleção, embora isto não seja permitido em itens da cláusula
FROM.
Nesses casos, cada nível de aninhamento é tratado em separado,
como se fosse um item
LATERAL ROWS FROM( ... ) separado.
Por exemplo, em
SELECT srf1(srf2(x), srf3(y)), srf4(srf5(z)) FROM tab;
as funções retornando conjunto srf2,
srf3 e srf5
seriam executadas pari passu com cada linha de
tab e, então,
srf1 e srf4
seriam aplicadas pari passu com cada linha produzida pelas
funções inferiores.
As funções retornando conjunto não podem ser usadas em construções
de avaliação condicional, como CASE ou
COALESCE.
Por exemplo, considere
SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;
Esta expressão pode parecer produzir cinco repetições para as
linhas de entrada tendo x > 0,
e uma única repetição para aquelas que não têm;
mas, na verdade, como generate_series(1, 5)
seria executado em um item LATERAL FROM implícito
antes da expressão CASE ser avaliada,
produziria cinco repetições de toda linha de entrada.
Para reduzir a confusão, estes casos produzem um erro de tempo de análise.
Se o último comando de uma função for INSERT,
UPDATE, DELETE ou
MERGE com RETURNING, este
comando sempre será executado até concluir, mesmo que a função
não seja declarada com SETOF, ou a consulta
chamadora não busque todas as linhas do resultado.
Quaisquer linhas extras produzidas pela cláusula
RETURNING são descartadas silenciosamente,
mas as modificações de tabela comandada ainda permanecem
(e estarão todas concluídas antes da função retornar).
Antes do PostgreSQL 10, colocar mais
de uma função retornando conjunto na mesma lista de seleção não
se comportava de forma muito consistente, a menos que sempre
produzissem o mesmo número de linhas.
Caso contrário, o que se obtinha era um número de linhas de saída
igual ao mínimo múltiplo comum dos números de linhas produzidas
pelas funções retornando conjunto.
Além disso, as funções retornando conjunto aninhadas não
funcionavam conforme descrito acima; em vez disso, uma função
retornando conjunto poderia ter no máximo um argumento de retorno
de conjunto, e cada aninhamento de funções retornando conjunto
era executado independentemente.
Além disso, a execução condicional (funções retornando conjunto
dentro de CASE etc.) era permitida
anteriormente, complicando ainda mais as coisas.
O uso da sintaxe LATERAL é recomendado ao
escrever consultas que precisam funcionar em versões mais antigas
do PostgreSQL, porque isto fornecerá
resultados consistentes em versões diferentes.
Se tiver uma consulta que dependa da execução condicional de uma
função retornando conjunto, poderá corrigi-la movendo o teste
condicional para uma função retornando conjunto personalizada.
Por exemplo,
SELECT x, CASE WHEN y > 0 THEN generate_series(1, z) ELSE 5 END FROM tab;
pode se tornar
CREATE FUNCTION case_generate_series(cond bool, start int, fin int, els int)
RETURNS SETOF int AS $$
BEGIN
IF cond THEN
RETURN QUERY SELECT generate_series(start, fin);
ELSE
RETURN QUERY SELECT els;
END IF;
END$$ LANGUAGE plpgsql;
SELECT x, case_generate_series(y > 0, 1, z, 5) FROM tab;
Esta formulação funciona da mesma forma em todas as versões do PostgreSQL.
Existe outra forma de declarar uma função como retornando um
conjunto, que é usar a sintaxe
RETURNS TABLE(.
Isto equivale a usar um ou mais parâmetros colunas)OUT,
além de marcar a função como retornando SETOF record
(ou SETOF um único tipo de dados de parâmetro
de saída, conforme apropriado).
Esta notação é especificada em versões recentes do padrão
SQL, portanto, pode ser mais portável do que
usar SETOF.
Por exemplo, o exemplo anterior de soma e produto também pode ser escrito dessa forma:
CREATE FUNCTION soma_prod_int_com_tab (x int)
RETURNS TABLE(soma int, produto int) AS $$
SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
Não é permitido usar parâmetros OUT ou
INOUT explícitos com a notação
RETURNS TABLE — todas as colunas de saída
devem ser colocadas na lista de TABLE.
As funções SQL podem ser declaradas aceitando e
retornando os tipos de dados polimórficos descritos em
Tipos de dados polimórficos.
A seguir está a função polimórfica make_array
que constrói uma matriz de dois elementos de tipo de dados arbitrários:
CREATE FUNCTION make_array(anyelement, anyelement) RETURNS anyarray AS $$
SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;
SELECT make_array(1, 2) AS intarray, make_array('a'::text, 'b') AS textarray;
intarray | textarray
----------+-----------
{1,2} | {a,b}
(1 linha)
Deve ser observado o uso da conversão de tipo de dados
'a'::text para especificar que o argumento é
do tipo de dados text.
Isto é necessário se o argumento for apenas um literal cadeia de
caracteres, caso contrário, seria tratado como do tipo de dados
unknown, e uma matriz de unknown não é
um tipo de dados válido.
Sem a conversão de tipo de dados, seria mostrado um erro como este:
ERRO: não foi possível determinar o tipo de dados polimórfico ↵
porque a entrada tem tipo de dados desconhecido
Com make_array declarado como acima, devem ser
fornecidos dois argumentos que sejam exatamente do mesmo tipo de
dados; o sistema não tentará resolver quaisquer diferenças de tipo
de dados.
Assim, por exemplo, isto não funciona:
SELECT make_array(1, 2.5) AS numericarray; ERRO: a função make_array(integer, numeric) não existe
Uma abordagem alternativa é usar a família “comum” de tipos de dados polimórficos, que permite ao sistema tentar identificar um tipo de dados comum adequado:
CREATE FUNCTION make_array2(anycompatible, anycompatible)
RETURNS anycompatiblearray AS $$
SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;
SELECT make_array2(1, 2.5) AS numericarray;
numericarray
--------------
{1,2.5}
(1 linha)
Como as regras para resolução de tipo de dados comum são padronizadas
para escolher o tipo de dados text quando todas as
entradas são de tipos de dados desconhecidos, a seguinte consulta
também funciona:
SELECT make_array2('a', 'b') AS textarray;
textarray
-----------
{a,b}
(1 linha)
É permitido ter argumentos polimórficos com o tipo de dados retornado fixo, mas o contrário não é. Por exemplo:
CREATE FUNCTION é_maior(anyelement, anyelement) RETURNS boolean AS $$
SELECT $1 > $2;
$$ LANGUAGE SQL;
SELECT é_maior(1, 2);
é_maior
---------
f
(1 linha)
CREATE FUNCTION func_inválida() RETURNS anyelement AS $$
SELECT 1;
$$ LANGUAGE SQL;
ERRO: não é possível determinar o tipo de dados do resultado
DETALHE: Um resultado do tipo de dados anyelement requer pelo menos uma
entrada do tipo de dados anyelement, anyarray, anynonarray,
anyenum, anyrange ou anymultirange.
O polimorfismo pode ser usado com funções que possuem argumentos de saída. Por exemplo:
CREATE FUNCTION dup (f1 anyelement, OUT f2 anyelement, OUT f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE SQL; SELECT * FROM dup(22);
f2 | f3
----+---------
22 | {22,22}
(1 linha)
O polimorfismo também pode ser usado com funções variádicas. Por exemplo:
CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$
SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;
SELECT anyleast(10, -1, 5, 4);
anyleast
----------
-1
(1 linha)
SELECT anyleast('abc'::text, 'def');
anyleast ---------- abc (1 linha)
CREATE FUNCTION concat_values(text, VARIADIC anyarray) RETURNS text AS $$
SELECT array_to_string($2, $1);
$$ LANGUAGE SQL;
SELECT concat_values('|', 1, 4, 2);
concat_values --------------- 1|4|2 (1 linha)
Quando uma função SQL tem um ou mais parâmetros de tipos de dados
ordenáveis, é identificada uma ordenação para cada chamada da
função, dependendo das ordenações atribuídas aos argumentos reais,
conforme descrito na Suporte a ordenação.
Se uma ordenação for identificada com sucesso
(ou seja, não há conflitos de ordenações implícitas entre os argumentos)
então, todos os parâmetros que podem ser ordenados são tratados
como tendo esta ordenação implicitamente.
Isto afeta o comportamento das operações sensíveis à ordenação
dentro da função.
Por exemplo, usando a função anyleast
descrita acima, o resultado de
SELECT anyleast('abc'::text, 'ABC');
é dependente da ordenação padrão do banco de dados.
Na localidade C o resultado será
ABC, mas em muitas outras localidades será
abc.
A ordenação a ser usada pode ser forçada adicionando a cláusula
COLLATE a qualquer um dos argumentos.
Por exemplo
SELECT anyleast('abc'::text, 'ABC' COLLATE "C");
Como alternativa, se for desejado que a função opere com uma
ordenação específica, independentemente de como ela é chamada,
devem ser inseridas cláusulas COLLATE conforme
necessário na definição da função.
A versão de anyleast a seguir sempre usa a
localidade en_US para comparar cadeias de caracteres:
CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$
SELECT min($1[i] COLLATE "en_US") FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;
Mas veja que isto gera um erro quando aplicado a um tipo de dados não ordenável.
Se não puder ser identificada nenhuma ordenação comum entre os argumentos reais, a função SQL tratará seus parâmetros como tendo a ordenação padrão de seus tipos de dados (que é geralmente a ordenação padrão do banco de dados, mas pode ser diferente para parâmetros de tipos de dados de domínio).
O comportamento de parâmetros ordenáveis pode ser pensado como uma forma limitada de polimorfismo, aplicável apenas a tipos de dados de texto.