41.3. Declarações #

41.3.1. Declaração de parâmetros de função
41.3.2. ALIAS
41.3.3. Cópia de tipo de dados
41.3.4. Tipos de dados linha
41.3.5. Tipos de dados registro
41.3.6. Ordenação das variáveis do PL/pgSQL

Todas as variáveis usadas em um bloco devem ser declaradas na seção de declarações do bloco. (As únicas exceções são a variável de laço, de um laço FOR iterando sobre um intervalo de valores inteiros, declarada automaticamente como uma variável inteira e, da mesma forma, a variável de laço, de um laço FOR iterando sobre o resultado de um cursor, declarada automaticamente como variável de registro.)

As variáveis do PL/pgSQL podem ter qualquer tipo de dados SQL, como integer, varchar e char.

Aqui estão alguns exemplos de declarações de variáveis:

id_usuario integer;
quantidade numeric(5);
url varchar;
minhalinha nomedatabela%ROWTYPE;
meucampo nomedatabela.nomedacoluna%TYPE;
umalinha RECORD;

A sintaxe geral de uma declaração de variável é:

nome [ CONSTANT ] tipo_de_dados [ COLLATE nome_da_ordenação ] [ NOT NULL ] [ { DEFAULT | := | = } expressão ];

A cláusula DEFAULT, se fornecida, especifica o valor inicial atribuído à variável ao entrar no bloco. Se a cláusula DEFAULT não for fornecida, a variável será inicializada com o valor nulo do SQL. A opção CONSTANT evita que a variável receba atribuição após ser inicializada, de forma que seu valor permaneça constante durante todo o bloco. A opção COLLATE especifica a ordenação a ser usada para a variável (veja Ordenação das variáveis do PL/pgSQL). Se for especificado NOT NULL, a atribuição de um valor nulo resultará em erro de tempo de execução. Todas as variáveis declaradas como NOT NULL devem ter um valor padrão não nulo especificado. Pode ser usado o sinal de igual (=), em vez do := compatível com o PL/SQL [123].

O valor padrão de uma variável é avaliado e atribuído à variável toda vez que entrar no bloco (não apenas uma vez por chamada de função). Assim, por exemplo, atribuir now() a uma variável do tipo de dados timestamp faz com que a variável receba a hora corrente da chamada da função, e não a hora em que a função foi pré-compilada.

Exemplos:

quantidade integer DEFAULT 32;
url varchar := 'http://mysite.com';
hora_da_transacao CONSTANT timestamp with time zone := now();

Uma vez declarada, o valor de uma variável pode ser usado em expressões de inicialização posteriores no mesmo bloco como, por exemplo:

DECLARE
  x integer := 1;
  y integer := x + 1;

41.3.1. Declaração de parâmetros de função #

Os parâmetros passados para as funções recebem os nomes identificadores $1, $2, etc. Opcionalmente, podem ser declarados aliases para os nomes dos parâmetros $n para melhorar a legibilidade. Tanto o alias quanto o identificador numérico podem ser usados para se referir ao valor do parâmetro.

Existem duas maneiras de criar um alias. A forma preferida é dando nome ao parâmetro no comando CREATE FUNCTION. Por exemplo:

CREATE FUNCTION imposto_vendas(subtotal real) RETURNS real AS $$
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

A outra maneira é declarar explicitamente o alias, usando a sintaxe de declaração

nome ALIAS FOR $n;

O mesmo exemplo escrito neste estilo se parece com:

CREATE FUNCTION imposto_vendas(real) RETURNS real AS $$
DECLARE
    subtotal ALIAS FOR $1;
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Nota

Estes dois exemplos não são perfeitamente equivalentes. No primeiro exemplo, subtotal pode ser referenciado como imposto_vendas.subtotal, mas no segundo exemplo não. (Se tivesse sido anexado um rótulo ao bloco interno, subtotal poderia ser qualificado com este rótulo.)

Mais alguns exemplos:

CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
DECLARE
    v_string ALIAS FOR $1;
    index ALIAS FOR $2;
BEGIN
    -- alguns cálculos usando v_string e index aqui
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION concat_campos_selec(in_t nome_alguma_tabela) RETURNS text AS $$
BEGIN
    RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;
$$ LANGUAGE plpgsql;

Quando uma função PL/pgSQL é declarada com parâmetros de saída, os parâmetros de saída recebem nomes $n e aliases opcionais, da mesma maneira que os parâmetros de entrada normais. Um parâmetro de saída é de fato uma variável cujo valor inicial é NULL; o valor deve ser atribuído durante a execução da função. É retornado o valor final do parâmetro. Por exemplo, a função imposto_vendas acima também pode ser declarada da seguinte forma:

CREATE FUNCTION imposto_vendas(subtotal real, OUT tax real) AS $$
BEGIN
    tax := subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

Note que foi omitido RETURNS real na função — poderia ter sido incluído, mas seria redundante.

Na chamada de uma função com parâmetros OUT, o(s) parâmetro(s) de saída devem ser omitidos:

SELECT imposto_vendas(100.00);

Parâmetros de saída são mais úteis quando são retornados vários valores. Um exemplo trivial é:

CREATE FUNCTION soma_produto(x int, y int, OUT soma int, OUT prod int) AS $$
BEGIN
    soma := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

SELECT * FROM soma_produto(2, 4);

 soma | prod
------+------
    6 |    8
(1 linha)

Conforme discutido em Funções SQL com parâmetros de saída, os parâmetros de saída criam de fato um tipo de dados registro anônimo para os resultados da função. Se for especificada a cláusula RETURNS, deverá ser RETURNS record.

Também funciona com procedimentos. Por exemplo [124]:

CREATE PROCEDURE soma_produto(x int, y int, OUT soma int, OUT prod int) AS $$
BEGIN
    soma := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

Na chamada do procedimento, todos os parâmetros devem ser especificados. Pode ser especificado NULL para os parâmetros de saída ao se chamar o procedimento em uma instrução SQL simples:

CALL soma_produto(2, 4, NULL, NULL);
 soma | prod
------+------
    6 |    8
(1 linha)

Entretanto, ao se chamar o procedimento a partir do código PL/pgSQL, deve ser declarada uma variável para cada parâmetro de saída; cada variável receberá o resultado da chamada. Veja Chamada de procedimento para obter detalhes.

Outra forma de declarar uma função PL/pgSQL é usando RETURNS TABLE. Por exemplo:

CREATE FUNCTION vendas_estendidas(p_itemno int)
RETURNS TABLE(quantidade int, total numeric) AS $$
BEGIN
    RETURN QUERY SELECT s.quantidade, s.quantidade * s.preco FROM vendas AS s
                 WHERE s.itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;

Esta forma equivale a declarar um ou mais parâmetros OUT, e especificar RETURNS SETOF algumtipo.

Quando o tipo de dados retornado por uma função PL/pgSQL é declarado como tipo de dados polimórfico (veja Tipos de dados polimórficos), é criado um parâmetro especial $0. Seu tipo de dados é o tipo de dados real retornado pela função, conforme deduzido dos tipos de dados de entrada reais. Isto permite que a função acesse seu tipo de dados real retornado, conforme mostrado em Cópia de tipo de dados. O parâmetro especial $0 é inicializado como nulo, podendo ser modificado pela função, portanto, pode ser usado para guardar o valor retornado se desejado, embora isto não seja obrigatório. $0 também pode receber um alias. Por exemplo, esta função funciona com qualquer tipo de dados que possua o operador +:

CREATE FUNCTION soma_tres_valores(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
    resultado ALIAS FOR $0;
BEGIN
    resultado := v1 + v2 + v3;
    RETURN resultado;
END;
$$ LANGUAGE plpgsql;

O mesmo efeito pode ser obtido declarando um ou mais parâmetros de saída com tipos de dados polimórficos. Neste caso, o parâmetro especial $0 não é usado; os próprios parâmetros de saída servem ao mesmo propósito. Por exemplo:

CREATE FUNCTION soma_tres_valores(v1 anyelement, v2 anyelement, v3 anyelement,
                                 OUT soma anyelement)
AS $$
BEGIN
    soma := v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

Na prática, pode ser mais útil declarar uma função polimórfica usando a família de tipos de dados anycompatible, para que ocorra a promoção automática dos argumentos de entrada para um tipo de dados comum. Por exemplo:

CREATE FUNCTION soma_tres_valores(v1 anycompatible, v2 anycompatible, v3 anycompatible)
RETURNS anycompatible AS $$
BEGIN
    RETURN v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

Neste exemplo, uma chamada como

SELECT soma_tres_valores(1, 2, 4.7);

 soma_tres_valores
-------------------
               7.7
(1 linha)

funciona, promovendo automaticamente as entradas de inteiro para numérico. Uma função usando o tipo de dados anyelement exigiria que se convertesse as três entradas para o mesmo tipo de dados manualmente.

41.3.2. ALIAS #

nome_novo ALIAS FOR nome_antigo;

A sintaxe de ALIAS é mais geral do que o sugerido na seção anterior: pode ser declarado um alias para qualquer variável, e não apenas para parâmetros de função. O principal uso prático para o alias é atribuir um nome diferente para variáveis com nomes predeterminados, como NEW ou OLD, numa função de gatilho.

Exemplos:

DECLARE
  anterior ALIAS FOR old;
  atualizado ALIAS FOR new;

Como o ALIAS cria duas maneiras diferentes para referenciar o mesmo objeto, o uso irrestrito pode ficar confuso. É melhor usá-lo apenas com a finalidade de substituir nomes predeterminados.

41.3.3. Cópia de tipo de dados #

nome tabela.coluna%TYPE
nome variável%TYPE

%TYPE fornece o tipo de dados de uma coluna de tabela ou de uma variável do PL/pgSQL previamente declarada. Pode-se usar isto para declarar variáveis ​​que armazenarão valores do banco de dados. Por exemplo, digamos que se tenha uma coluna chamada user_id na tabela users. Para declarar uma variável com o mesmo tipo de dados que users.user_id se escreve:

user_id users.user_id%TYPE;

Também é possível escrever uma especificação de matriz após %TYPE, criando assim uma variável que armazena uma matriz do tipo de dados referenciado:

user_ids users.user_id%TYPE[];
user_ids users.user_id%TYPE ARRAY[4];  -- equivalente à acima

Assim como na declaração de colunas de tabela que são matrizes, não importa se são escritos vários pares de colchetes ou dimensões de matriz específicas: O PostgreSQL trata todas as matrizes de um determinado tipo de dados de elemento como sendo do mesmo tipo de dados, independentemente da dimensionalidade. (Veja Declaração dos tipos de dados matriz.)

Ao usar %TYPE não é necessário conhecer o tipo de dados da estrutura que se está referenciando, e mais importante, se o tipo de dados do item referenciado mudar no futuro (por exemplo: o tipo de dados de user_id for mudado de integer para real), talvez não seja necessário alterar a definição da função.

%TYPE é particularmente valioso em funções polimórficas, porque os tipos de dados necessários para as variáveis internas podem mudar de uma chamada para outra. Podem ser criadas variáveis apropriadas aplicando %TYPE aos argumentos da função, ou a marcadores de lugar (placeholders) do resultado.

41.3.4. Tipos de dados linha #

nome nome_da_tabela%ROWTYPE;
nome nome_do_tipo_de_dados_composto;

Uma variável de tipo de dados composto é chamada de variável linha (ou uma variável tipo-linha). Esta variável pode conter uma linha inteira do resultado da consulta SELECT ou FOR, desde que o conjunto de colunas dessa consulta corresponda ao tipo de dados declarado da variável. Os valores individuais dos campos da linha são acessados usando a notação de ponto usual, por exemplo, var_linha.campo.

Uma variável tipo-linha pode ser declarada tendo o mesmo tipo de dados das linhas de uma tabela ou visão existente, usando a notação nome_da_tabela%ROWTYPE; ou pode ser declarada fornecendo o nome de um tipo de dados composto. (Uma vez que toda tabela tem um tipo de dados composto associado com o mesmo nome da tabela, na verdade, não importa para o PostgreSQL se for escrito %ROWTYPE ou não, mas a forma com %ROWTYPE é mais portável.)

Assim como em %TYPE, %ROWTYPE pode ser seguido por uma declaração de matriz para declarar uma variável que armazena uma matriz do tipo de dados composto referenciado.

Os parâmetros de uma função podem ser de tipos de dados compostos (linhas inteiras da tabela). Neste caso, o identificador correspondente $n será uma variável tipo-linha, e os campos podem ser selecionados a partir dela, por exemplo, $1.user_id.

A seguir está um exemplo de uso de tipos de dados compostos. tabela1 e tabela2 são tabelas existentes com ao menos os campos mencionados:

CREATE FUNCTION concatena_campos(t_linha tabela1) RETURNS text AS $$
DECLARE
    t2_linha tabela2%ROWTYPE;
BEGIN
    SELECT * INTO t2_linha FROM tabela2 WHERE ... ;
    RETURN t_linha.f1 || t2_linha.f3 || t_linha.f5 || t2_linha.f7;
END;
$$ LANGUAGE plpgsql;

SELECT concatena_campos(t.*) FROM tabela1 t WHERE ... ;

41.3.5. Tipos de dados registro #

nome RECORD;

As variáveis tipo-registro são semelhantes às variáveis tipo-linha, mas não possuem uma estrutura predefinida. Elas assumem a estrutura da linha que lhes foi atribuída durante o comando SELECT ou FOR. A subestrutura de uma variável tipo-registro pode mudar cada vez que a variável receber uma atribuição. Uma consequência disso é que, até que a variável tipo-registro tenha recebido uma atribuição pela primeira vez, ela não possui subestrutura, e qualquer tentativa de acessar um campo dela resultará em erro de tempo de execução.

Note que RECORD não é um tipo de dados verdadeiro, é apenas um marcador de lugar (placeholder). Deve-se, também, perceber que quando uma função PL/pgSQL é declarada retornando o tipo de dados record, este não é exatamente o mesmo conceito de uma variável do tipo-registro, embora a função possa usar uma variável do tipo-registro para armazenar seu resultado. Nos dois casos, a estrutura real da linha é desconhecida quando do momento da declaração, mas no caso de uma função que retorna record, a estrutura real é determinada no momento de análise da chamada da consulta, enquanto uma variável do tipo-registro pode alterar sua estrutura de linha dinamicamente.

41.3.6. Ordenação das variáveis do PL/pgSQL #

Quando uma função PL/pgSQL possui um ou mais parâmetros com tipos de dados ordenáveis, é identificada uma ordenação (collation) para cada chamada da função, dependendo das ordenações atribuídas aos argumentos reais, conforme descrito em Suporte a ordenação. Se uma ordenação for identificada com êxito (ou seja, não houver conflitos de ordenações implícitos entre os argumentos), todos os parâmetros que podem ser ordenados serão tratados como tendo este ordenamento implicitamente. Isto afeta o comportamento das operações sensíveis à ordenação dentro da função. Por exemplo, considere

CREATE FUNCTION menor_que(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b;
END;
$$ LANGUAGE plpgsql;

SELECT menor_que(text_field_1, text_field_2) FROM table1;
SELECT menor_que(text_field_1, text_field_2 COLLATE "C") FROM table1;

A primeira chamada da função menor_que usará a ordenação comum de text_field_1 e text_field_2 para a comparação, enquanto a segunda chamada usará a ordenação C.

Além disso, a ordenação identificada também é assumida como a ordenação de quaisquer variáveis locais que sejam de tipos de dados ordenáveis. Portanto, esta função não funcionaria de maneira diferente se fosse escrita como

CREATE FUNCTION menor_que(a text, b text) RETURNS boolean AS $$
DECLARE
    local_a text := a;
    local_b text := b;
BEGIN
    RETURN local_a < local_b;
END;
$$ LANGUAGE plpgsql;

Não havendo parâmetros de tipos de dados ordenáveis, ou nenhuma ordenação comum puder ser identificada para eles, os parâmetros e as variáveis locais usarão a ordenação padrão de seu tipo de dados (que é geralmente a ordenação padrão do banco de dados, mas pode ser diferente para variáveis de tipos de dados de domínio).

Uma variável local de um tipo de dados ordenável pode ter uma ordenação diferente associada a ela, incluindo a opção COLLATE em sua declaração. Por exemplo:

DECLARE
    local_a text COLLATE "en_US";

Esta opção substitui a ordenação que seria dada à variável segundo as regras acima.

Além disso, é claro que podem ser escritas cláusulas COLLATE explícitas dentro da função, se for desejado forçar o uso de uma ordenação específica em uma operação específica. Por exemplo:

CREATE FUNCTION menor_que_c(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b COLLATE "C";
END;
$$ LANGUAGE plpgsql;

SELECT menor_que_c ('ABC', 'abc');

 menor_que_c
-------------
 t
(1 linha)

Esta cláusula substitui as ordenações associados às colunas da tabela, parâmetros ou variáveis locais usadas na expressão, exatamente como aconteceria em um comando SQL simples.

Exemplo 41.1. Exemplo do tradutor

Comparação usando a ordenação pt_BR

Neste exemplo é criada a função menor_que_pt_br, que compara os dois argumentos da entrada usando a ordenação pt_BR, para comparar com o resultado da função menor_que_c, que usa a ordenação C. Como pode ser visto pelos resultados, na ordenação C ABC é menor que (vem antes de) abc, enquanto na ordenação pt_BR se dá o contrário.

CREATE FUNCTION menor_que_pt_br(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b COLLATE "pt_BR";
END;
$$ LANGUAGE plpgsql;

SELECT menor_que_pt_BR ('ABC', 'abc');
 menor_que_pt_br
-----------------
 f
(1 linha)



[123] PL/SQL significa Extensões de Linguagem Procedural para a Linguagem de Consulta Estruturada (Procedural Language Extensions to the Structured Query Language). O PL/SQL adiciona muitas construções procedurais à linguagem SQL para superar algumas limitações do SQL. Além disso, o PL/SQL fornece uma solução de linguagem de programação mais abrangente para criar aplicações de missão crítica em bancos de dados Oracle. Oracle PL/SQL Tutorial (N. T.)

[124] Não pode existir uma função e um procedimento com o mesmo nome. Para o procedimento ser criado, a função com o mesmo nome deve ser excluída. (N. T.)