7.1.6. Execução de Comandos SQL
Os comandos SQL podem ser utilizados como instruções válidas dentro de funções PL/pgSQL
. Esses comandos podem utilizar nomes de variáveis definidas na própria função para compor o comando.
Para ilustrar a execução de comandos, vamos criar uma tabela chamada pluviometros
:
CREATE TABLE pluviometros
(
gid SERIAL PRIMARY KEY,
location GEOMETRY(POINT, 4326)
);
7.1.6.1. Comandos que não retornam resultados
Vamos criar uma função em PL/pgSQL
que faça a inserção na tabela pluviometros
tomando como entrada os valores de longitude e latitude onde se encontra instalado um pluviômetro:
CREATE OR REPLACE FUNCTION my_insert(longitude NUMERIC, latitude NUMERIC)
RETURNS VOID
AS
$$
BEGIN
INSERT INTO pluviometros(location)
VALUES( ST_Point(longitude, latitude, 4326) );
RETURN;
END;
$$
LANGUAGE plpgsql;
Agora, é possível chamar a função my_insert
que irá montar a tupla a ser inserida na tabela pluviometros
:
SELECT my_insert(-45.8872, -23.1791);
Repare que o comando INSERT
sem a cláusula RETURNING
não retorna nenhum resultado. Para realizar a avaliação de expressões ou consultas do tipo SELECT
descartando o resultado, pode-se utilizar o comando PERFORM
. Este comando é necessário, principalmente, quando se tem uma consulta do tipo SELECT
cuja finalidade é apenas produzir um efeito colateral (side-effect).
PERFORM SELECT * FROM pluviometros ORDER BY gid;
Aviso
Se você quiser testar o comando acima, deverá incluí-lo dentro de uma função PL/pgSQL
.
7.1.6.2. Comandos que retornam uma única tupla como resultado
Podemos também recuperar o resultado de uma consulta que retorne uma única tupla, ou seja, um resultado que pode ser composto de um ou mais valores.
Como exemplo desse tipo de comando, vamos alterar a função my_insert
para que ela retorne como resultado o valor gerado automaticamente pela sequência (SEQUENCE
) associada à coluna gid
, que é a chave primária da tabela pluviometros
.
Nota
Provavelmete você terá que remover a definição da função my_insert
realizada anteriormente. Para tal, utilize o seguinte comando:
DROP FUNCTION my_insert(numeric,numeric);
CREATE OR REPLACE FUNCTION my_insert(longitude NUMERIC, latitude NUMERIC)
RETURNS pluviometros.gid%TYPE
AS
$$
DECLARE
id pluviometros.gid%TYPE;
BEGIN
INSERT INTO pluviometros(location)
VALUES( ST_Point(longitude, latitude, 4326) )
RETURNING gid INTO id;
RETURN id;
END;
$$
LANGUAGE plpgsql;
Com essa modificação, ao invocarmos a função my_insert
obtemos o novo identificador da linha criada:
SELECT my_insert(-43.6419, -20.393);
Saída:
my_insert
-----------
2
(1 row)
Repare na declaração do tipo de retorno da função e no tipo da variável id
. Utilizamos %TYPE
para deduzir o tipo através de uma referência ao nome da coluna gid
da tabela pluviometros
.
Na prática, podemos associar o resultado de comandos SQL que produzam uma única tupla, a uma variável do tipo RECORD
, tipos compostos (como os tipos definidos por tabelas e %ROWTYPE
) ou listas de variáveis compatíveis com os tipos retornados. A sintaxe de cada comando é a seguinte:
SELECT ... INTO [STRICT] variável[, variável ...] FROM ...;
INSERT ... RETURNING expressões INTO [STRICT] variável[, variável ...];
UPDATE ... RETURNING expressões INTO [STRICT] variável[, variável ...];
DELETE ... RETURNING expressões INTO [STRICT] variável[, variável ...];
Caso a palavra STRICT
seja omitida em comandos SELECT
, a variável será associada ao primeiro valor retornado pelo comando ou a um valor nulo. Se a palavra STRICT
for utilizada, a consulta deverá retornar uma única linha, caso contrário, será produzida uma exceção que irá interromper a execução da função. Para os comandos INSERT
, UPDATE
e DELETE
usando a cláusula RETURNING
, mesmo omitindo a palavra STRICT
, será lançada uma exceção se mais de uma tupla for retornada1.
Uma variável que é automaticamente definida quando executamos um comando que retorna alguma tupla como resultado, é a variável FOUND
. No caso de comandos que não usam a palavra STRICT
, a próxima instrução da função pode verificar o valor desta variável para saber se alguma tupla foi ou não retornada. Por exemplo, se quisermos saber se existe algum pluviômetro a uma certa distância de uma dada localização, podemos construir uma função como a seguinte2:
CREATE OR REPLACE FUNCTION existe_pluviometro(location GEOMETRY,
distance NUMERIC)
RETURNS BOOL
AS
$$
DECLARE
pluviometro pluviometros%ROWTYPE;
BEGIN
SELECT * INTO pluviometro
FROM pluviometros
WHERE ST_DWithin(pluviometros.location,
existe_pluviometro.location,
distance);
IF FOUND THEN
RAISE NOTICE
'Encontrado pelo menos um pluviometro próximo: %',
ST_AsText(pluviometro.location);
RETURN TRUE;
ELSE
RAISE NOTICE
'Não foi encontrado um único pluviômetro nas proximidades de: %',
ST_AsText(existe_pluviometro.location);
RETURN FALSE;
END IF;
END;
$$
LANGUAGE plpgsql;
Se quisermos saber da existência ou não de um pluviômetro num raio de 1.0 grau das localizações (-45, -23)
e (-47, -25)
, podemos realizar a seguinte consulta:
SELECT existe_pluviometro(ST_SetSRID(ST_MakePoint(-45, -23), 4326), 1.0);
Saída:
existe_pluviometro
--------------------
t
(1 row)
SELECT existe_pluviometro(ST_SetSRID(ST_MakePoint(-47, -25), 4326), 1.0);
Saída:
NOTICE: Não foi encontrado um único pluviômetro nas proximidades de: POINT(-47 -25)
existe_pluviometro
--------------------
f
(1 row)
É muito comum usarmos a variável FOUND
junto com testes condicionas para lançar uma exceção:
...
IF NOT FOUND THEN
RAISE EXCEPTION 'mensagem: nenhuma linha encontrada!';
END IF;
...
No caso de comandos que usam a palavra STRICT
, existem outras duas variáveis muito úteis:
NO_DATA_FOUND
: indica que nenhuma tupla foi retornada.TOO_MANY_ROWS
: indica que mais do que uma tupla foi retornada.
Podemos utilizar um bloco para capturar os possíveis tipos de exceções, como mostrado na função abaixo, que verifica se existe apenas um único pluviômetro a uma certa distância de uma dada localização:
CREATE OR REPLACE FUNCTION unico_pluviometro(location GEOMETRY,
distance NUMERIC)
RETURNS BOOL
AS
$$
DECLARE
pluviometro pluviometros%ROWTYPE;
BEGIN
SELECT * INTO STRICT pluviometro
FROM pluviometros
WHERE ST_DWithin(pluviometros.location,
unico_pluviometro.location,
distance);
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE NOTICE 'Nenhum pluviômetro encontrado próximo a: %',
ST_AsText(unico_pluviometro.location);
RETURN FALSE;
WHEN TOO_MANY_ROWS THEN
RAISE NOTICE 'Vários pluviômetros encontrados próximo a: %',
ST_AsText(unico_pluviometro.location);
RETURN FALSE;
END;
$$
LANGUAGE plpgsql;
Para testar a função acima, execute os comandos abaixo:
SELECT unico_pluviometro(ST_SetSRID(ST_MakePoint(-45, -23), 4326), 1.0);
Saída:
unico_pluviometro
-------------------
t
(1 row)
SELECT unico_pluviometro(ST_SetSRID(ST_MakePoint(-47, -25.), 4326), 1.0);
Saída:
unico_pluviometro
-------------------
f
(1 row)
Aviso
Atente-se ao fato de que a variável FOUND
é sempre associada ao valor TRUE
quando STRICT
é utilizada e o comando retorna uma única tupla.
7.1.6.3. Comandos Dinâmicos
Outra facilidade para criação de funções em PL/pgSQL
é a possibilidade de construirmos dinamicamente uma string representando um comando SQL para posterior execução. Vamos ilustrar o uso do comando EXECUTE
com um exemplo que irá posicionar um pluviômetro em uma localização aleatória.
CREATE OR REPLACE FUNCTION random_insert()
RETURNS pluviometros.gid%TYPE
AS
$$
DECLARE
id pluviometros.gid%TYPE;
r NUMERIC;
longitude NUMERIC;
latitude NUMERIC;
pt GEOMETRY;
query TEXT;
BEGIN
query := 'INSERT INTO pluviometros (location) VALUES($1) RETURNING gid';
r := random();
longitude := 360.0 * r - 180.0;
latitude := 180.0 * r - 90.0;
RAISE NOTICE 'Localização: (%, %)', longitude, latitude;
pt := ST_SetSRID(ST_MakePoint(longitude, latitude), 4326);
EXECUTE query INTO STRICT id USING pt;
RETURN id;
END;
$$
LANGUAGE plpgsql;
Executando a função random_insert
obtemos um resultado como:
SELECT random_insert();
Saída:
NOTICE: Localização: (17.3685365170239600, 8.6842682585119800)
random_insert
---------------
3
(1 row)
Aviso
Os parâmetros como $1
e $2
só podem ser usados para os valores de atributos. Não é possível utilizá-los para substituir nomes de tabelas ou colunas. Neste caso, devemos concatenar o texto do comando ou usar a função format()
com o caractere de substituição de nomes de tabelas e colunas (%I
).
Notas
- 1
A explicação dada pelo time de desenvolvimento do PostgreSQL é que neste caso não há a possibilidade de usar uma cláusula como
ORDER BY
que possibilite a escolha da linha afetada a ser considerada.- 2
Obviamente não precisamos de uma função para isso, além de podermos escrever uma consulta de agregação com o operador
COUNT(*)
.