Implementando Stored Procedures; utilizando cursores para buscar dados; controlando o fluxo da procedure; tratando os erros corretamente; aplicando e praticando programação com Stored Procedures.
- Criando funções
- Tipos e funções
- Linguagem Procedural
- Estruturas de Controle
- Estruturas de repetição
- Mão na massa
Saiba mais sobre o curso aqui ou acompanhe minhas anotações abaixo. ⬇️
Para programar com banco de dados, utilizam-se funções. As funções são uma extensão da linguagem SQL para complementar as consultas. A linguagem SQL não é uma linguagem de programação, pois não possui estruturas de decisão, repetição, variáveis, etc.
O comando CREATE FUNCTION
é utilizado para criar uma função que, em sua forma mais simples, não traz parâmetros e precisa retornar algo. Além disso, é preciso dizer a linguagem usada para criar a função.
Por padrão, o nome da coluna retornada em uma função que nos entrega um único valor é o mesmo nome da função. Nós podemos facilmente mudar o nome dessa coluna utilizando o AS, que cria um alias.
-- Criando a função que retorna um inteiro usando a linguagem sql
CREATE FUNCTION primeira_funcao() RETURNS INTEGER AS '
SELECT (5 - 3) * 2
' LANGUAGE SQL;
-- Chama a função e retorna o inteiro com uma coluna de mesmo nome
SELECT primeira_funcao();
-- Chama a função e retorna o inteiro com uma coluna renomeada
SELECT primeira_funcao() AS numero;
-- É possível chamar a função como uma tabela também
SELECT * FROM primeira_funcao();
Quando utiliza-se parâmetros na função, o nome é opcional, mas é necessário dizer o tipo de dado que será usado. O nome é opcional, pois pode-se utilizar a posição do parâmetro na função criada.
Propósito do parâmetro no função:
Através de parâmetros nós podemos receber informações de fora da função (como o ID de um registro, por exemplo) e manipular estes dados dentro da função
-- Cria a função com dois parâmetros e seu tipo para utilizá-los inputando dados
CREATE FUNCTION soma_dois_numeros(numero_1 INTEGER, numero_2 INTEGER)
RETURNS INTEGER AS '
SELECT numero_1 + numero_2
' LANGUAGE SQL;
SELECT soma_dois_numeros(2, 2);
-- Uma das formas de reutilizar uma função é excluir e recriar ela
DROP FUNCTION soma_dois_numeros;
-- Cria a função com dois parâmetros só com o tipo de dado e utilizando sua posição
CREATE FUNCTION soma_dois_numeros(INTEGER, INTEGER)
RETURNS INTEGER AS '
SELECT $1 + $2
' LANGUAGE SQL;
SELECT soma_dois_numeros(3, 17);
O que define o valor que será retornado em uma função?
Após realizar todas as instruções, a última query tem seu resultado obtido pelo PostgreSQL
e, desse resultado, a primeira linha é retornada para quem chamou a função.
- O último comando de uma função precisa informar o valor, precisa trazer o valor que se quer retornar.
- O que vai ser utilizado como retorno é a primeira linha desse último comando. Sempre o primeiro item.
CREATE TABLE a (nome VARCHAR(255) NOT NULL);
CREATE FUNCTION cria_a(nome VARCHAR) RETURNS VARCHAR AS '
INSERT INTO a (nome) VALUES (cria_a.nome);
SELECT nome;
' LANGUAGE SQL;
Restrição ao substituir uma função:
Nós não podemos alterar (REPLACE) uma função informando tipos diferentes em seus parâmetros ou retornos. Para atingir este objetivo precisamos excluir e criar esta função.
-- Alterando ou substituindo uma função, sem precisar excluir
CREATE OR REPLACE FUNCTION cria_a(nome VARCHAR) RETURNS VARCHAR AS '
INSERT INTO a (nome) VALUES (cria_a.nome);
SELECT nome;
' LANGUAGE SQL;
SELECT cria_a('Vinicius Dias');
Para criar funções que não necessitam de um retorno, basta substituir o comando após o RETURNS
para void
. Isso faz com que, ao chamar a função, ela retorne um valor nulo.
Importante saber que o comando void não permite replace, então é preciso excluir e recriar a função.
DROP FUNCTION cria_a;
CREATE FUNCTION cria_a(nome VARCHAR) RETURNS void AS '
INSERT INTO a (nome) VALUES (cria_a.nome);
' LANGUAGE SQL;
SELECT cria_a('Vinicius Dias');
Quando se quer definir um valor no insert como varchar, por exemplo, as aspas simples da função conflitam com as aspas simples do valor. Para resolver isso, existe outra forma de definir a criação da função. Utiliza-se $$
.
DROP FUNCTION cria_a;
CREATE FUNCTION cria_a(nome VARCHAR) RETURNS void AS $$
INSERT INTO a (nome) VALUES ('Patrícia');
$$ LANGUAGE SQL;
Para saber mais: Procedure Para saber mais: Returning
Ao utilizar uma tabela como parâmetro, define-se o campo que irá receber a função dentro da própria função.
CREATE TABLE instrutor (
id SERIAL PRIMARY KEY,
nome VARCHAR(255) NOT NULL,
salario DECIMAL(10, 2)
);
INSERT INTO instrutor (nome, salario) VALUES ('Vinicius Dias', 100);
CREATE FUNCTION dobro_do_salario(instrutor) RETURNS DECIMAL AS $$
SELECT $1.salario * 2 AS dobro;
$$ LANGUAGE SQL;
SELECT nome, dobro_do_salario(instrutor.*) FROM instrutor;
O retorno composto utiliza os campos da tabela para trazer a primeira linha como um valor ou como um conjunto de valores.
Como um valor:
Usamos como um único valor quando o retorno da função for um tipo simples (após o SELECT).
CREATE OR REPLACE FUNCTION cria_instrutor_falso() RETURNS instrutor AS $$
SELECT 22 AS id, 'Nome falso' AS nome, 200.00 AS salario;
$$ LANGUAGE SQL;
SELECT cria_instrutor_falso();
Como um conjunto de valores:
Usamos como um conjunto de valores quando o retorno da função for um tipo composto (após o FROM).
CREATE OR REPLACE FUNCTION cria_instrutor_falso() RETURNS instrutor AS $$
SELECT 22, 'Nome falso', 200::DECIMAL;
$$ LANGUAGE SQL;
SELECT * FROM cria_instrutor_falso();
- Ao criar a tabela, foi definido que o salário seria um valor decimal. Então, ao definir o salário na função é necessário que seja nesse formato. Acima estão duas formas de trazer tal notação.
- Como a função está retornando a tabela instrutor como tipo, não é necessário colocar o nome dos campos na função, porém fica mais legível se tiverem descritos.
-- Inserindo mais dados na tabela
INSERT INTO instrutor (nome, salario) VALUES ('Diogo Mascarenhas', 200);
INSERT INTO instrutor (nome, salario) VALUES ('Nico Steppat', 300);
INSERT INTO instrutor (nome, salario) VALUES ('Juliana Oliveira', 400);
INSERT INTO instrutor (nome, salario) VALUES ('Príscila Saraiva', 500);
CREATE FUNCTION instrutores_bem_pagos(valor_salario DECIMAL)
RETURNS SETOF instrutor AS $$
SELECT * FROM instrutor WHERE salario > valor_salario;
$$ LANGUAGE SQL;
SELECT * FROM instrutores_bem_pagos(300);
Nós podemos definir a estrutura de uma tabela especificamente para o retorno de uma função. Isso é utilizado quando não queremos criar um tipo (ou tabela) específico para um retorno.
DROP FUNCTION instrutores_bem_pagos;
CREATE FUNCTION instrutores_bem_pagos(valor_salario DECIMAL)
RETURNS TABLE (id INTEGER, nome VARCHAR, salario DECIMAL) AS $$
SELECT * FROM instrutor WHERE salario > valor_salario;
$$ LANGUAGE SQL;
SELECT * FROM instrutores_bem_pagos(300);
- O record retorna uma linha genérica para o registro. Com ele é necessário definir as colunas que se quer retornar.
DROP FUNCTION instrutores_bem_pagos;
CREATE FUNCTION instrutores_bem_pagos(valor_salario DECIMAL)
RETURNS SETOF record AS $$
SELECT * FROM instrutor WHERE salario > valor_salario;
$$ LANGUAGE SQL;
SELECT * FROM instrutores_bem_pagos(300);
Pode-se definir parâmetros de saída para retornar valores compostos. Além de definir os parâmetros de entrada no IN
, o comando OUT
funciona semelhante a criaçao de tipo para retornar valores compostos nos registros.
-- Utilizando parâmetros de saída para retornar valores compostos
CREATE FUNCTION soma_e_produto (
IN numero_1 INTEGER,
IN numero_2 INTEGER,
OUT soma INTEGER,
OUT produto INTEGER
) AS $$
SELECT numero_1 + numero_2 AS soma, numero_1 * numero_2 AS produto;
$$ LANGUAGE SQL;
SELECT * FROM soma_e_produto(3, 3);
-- Criando tipo para retornar valores compostos
CREATE TYPE dois_valores AS (soma INTEGER, produto INTEGER);
DROP FUNCTION soma_e_produto;
CREATE FUNCTION soma_e_produto (IN numero_1 INTEGER, IN numero_2 INTEGER)
RETURNS dois_valores AS $$
SELECT numero_1 + numero_2 AS soma, numero_1 * numero_2 AS produto;
$$ LANGUAGE SQL;
SELECT * FROM soma_e_produto(3, 3);
Então, para utilizar o record
de maneira funcional, definindo as colunas que retornarão os registros, pode-se utilizar os parâmetros de saída em conjunto com ele.
DROP FUNCTION instrutores_bem_pagos;
CREATE FUNCTION instrutores_bem_pagos(
valor_salario DECIMAL,
OUT nome VARCHAR,
OUT salario DECIMAL
)
RETURNS SETOF record AS $$
SELECT nome, salario FROM instrutor WHERE salario > valor_salario;
$$ LANGUAGE SQL;
SELECT * FROM instrutores_bem_pagos(300);
A PLPGSQL ou PL/pgSQL é uma linguagem estrutural estendida da SQL que tem por objetivo auxiliar as tarefas de programação no PostgreSQL. Ela incorpora à SQL características procedurais, como os benefícios e facilidades de controle de fluxo de programas que as melhores linguagens possuem. - Wikipédia
A estrutura PLpgSQL difere da estrutura SQL porque não, necessariamente, o último comando é executado. Na verdade, a PL precisa do RETURN
para realmente retornar o que foi definido. Precisa também, das cláusulas BEGIN
e END
delimitando o corpo da função.
CREATE OR REPLACE FUNCTION primeira_pl() RETURNS INTEGER AS $$
BEGIN
-- Vários comandos em SQL
RETURN 1;
END
$$ LANGUAGE plpgsql;
SELECT primeira_pl();
Diferenças básicas entre SQL e PLpgSQL:
- PLpgSQL informa de forma explícita o que vai retornar. Funções em SQL retornam o resultado da última query.
- PLpgSQL precisa ter blocos definidos, enquanto funções em SQL não.
O símbolo :=
atribui um valor para uma variável. Difere do igual porque serve apenas para atribuição, enquanto o =
pode ser utilizado para comparação. O DEFAULT
funciona da mesma maneira, mas pode ser utilizado apenas para declarar a variável, dentro do corpo da função não funciona.
Para saber mais: Default
CREATE OR REPLACE FUNCTION primeira_pl() RETURNS INTEGER AS $$
DECLARE
primeira_variavel INTEGER DEFAULT 3;
BEGIN
primeira_variavel := primeira_variavel * 2;
RETURN primeira_variavel;
END
$$ LANGUAGE plpgsql;
SELECT primeira_pl();
Caso queira declarar uma variável dentro do bloco já existente, o PL entende como se fosse uma nova variável e apenas armazena ela.
CREATE OR REPLACE FUNCTION primeira_pl() RETURNS INTEGER AS $$
DECLARE
primeira_variavel INTEGER DEFAULT 3;
BEGIN
primeira_variavel := primeira_variavel * 2;
DECLARE
primeira_variavel INTEGER;
BEGIN
primeira_variavel := 7;
END;
RETURN primeira_variavel;
END
$$ LANGUAGE plpgsql;
SELECT primeira_pl();
Para que essa variável se torne acessível dentro do bloco pai, basta não declará-la, apenas iniciar e finalizar já retorna seu valor.
CREATE OR REPLACE FUNCTION primeira_pl() RETURNS INTEGER AS $$
DECLARE
primeira_variavel INTEGER DEFAULT 3;
BEGIN
primeira_variavel := primeira_variavel * 2;
BEGIN
primeira_variavel := 7;
END;
RETURN primeira_variavel;
END
$$ LANGUAGE plpgsql;
SELECT primeira_pl();
Por padrão, por boas práticas, evite a todo custo definir duas variáveis com o mesmo nome em blocos diferentes.
O que é um escopo de variáveis?
É o bloco onde uma variável “vive”. Fora deste bloco, ela não existe, ou seja, está fora de seu escopo.
Uma variável declarada em um bloco mais interno não pode ser acessada em um bloco mais externo. Variáveis com o mesmo nome podem existir em diferentes escopos.
Diferença no retorno do registro que vem vazio ao invés de nulo:
DROP FUNCTION cria_a;
CREATE OR REPLACE FUNCTION cria_a(nome VARCHAR) RETURNS void AS $$
BEGIN
INSERT INTO a (nome) VALUES ('Patricia');
END
$$ LANGUAGE plpgsql;
SELECT cria_a('Vinicius Dias');
Retorna uma linha com os registros, referenciando o tipo instrutor:
CREATE OR REPLACE FUNCTION cria_instrutor_falso() RETURNS instrutor AS $$
BEGIN
RETURN ROW(22, 'Nome falso', 200::DECIMAL)::instrutor;
END
$$ LANGUAGE plpgsql;
SELECT id, salario FROM cria_instrutor_falso();
Declara uma variável para inserir na consulta e retorna ela:
CREATE OR REPLACE FUNCTION cria_instrutor_falso() RETURNS instrutor AS $$
DECLARE
retorno instrutor;
BEGIN
SELECT 22, 'Nome falso', 200::DECIMAL INTO retorno;
RETURN retorno;
END
$$ LANGUAGE plpgsql;
SELECT id, salario FROM cria_instrutor_falso();
Retorna a consulta do select em si
DROP FUNCTION instrutores_bem_pagos;
CREATE FUNCTION instrutores_bem_pagos(valor_salario DECIMAL)
RETURNS SETOF instrutor AS $$
BEGIN
RETURN QUERY SELECT * FROM instrutor WHERE salario > valor_salario;
END
$$ LANGUAGE plpgsql;
SELECT * FROM instrutores_bem_pagos(300);
Qual o propósito ou funcionalidade da instrução SELECT INTO?
SELECT INTO atribui o resultado de uma query a uma variável.
Para saber mais: Sintaxe
A consulta abaixo é mais rápida que a posterior, pois vai direto ao ponto. Com apenas uma consulta, chega ao mesmo resultado. Utiliza uma condição para retornar os registros.
CREATE FUNCTION salario_ok(instrutor instrutor)
RETURNS VARCHAR AS $$
BEGIN
-- Se o salário do instrutor for maior do que 200, ok
IF instrutor.salario > 200 THEN
RETURN 'Salário está ok';
-- Senão, pode aumentar
ELSE
RETURN 'Salário pode aumentar';
END IF;
END
$$ LANGUAGE plpgsql;
SELECT nome, salario_ok(instrutor) FROM instrutor;
A query abaixo entrega o mesmo resultado, porém com perda de performance, pois executa mais de uma consulta na mesma função. Existe uma condição e uma consulta extra pelo id.
DROP FUNCTION salario_ok;
CREATE FUNCTION salario_ok(id_instrutor INTEGER)
RETURNS VARCHAR AS $$
DECLARE
instrutor instrutor;
BEGIN
SELECT * FROM instrutor WHERE id = id_instrutor INTO instrutor;
-- Se o salário do instrutor for maior do que 200, ok
IF instrutor.salario > 200 THEN
RETURN 'Salário está ok';
-- Senão, pode aumentar
ELSE
RETURN 'Salário pode aumentar';
END IF;
END
$$ LANGUAGE plpgsql;
SELECT nome, salario_ok(instrutor.id) FROM instrutor;
No caso de várias condições, pode-se utilizar o ELSEIF
para reduzir e deixar o código mais legível.
CREATE OR REPLACE FUNCTION salario_ok(id_instrutor INTEGER)
RETURNS VARCHAR AS $$
DECLARE
instrutor instrutor;
BEGIN
SELECT * FROM instrutor WHERE id = id_instrutor INTO instrutor;
-- Se o salário do instrutor for maior do que 300, ok
IF instrutor.salario > 300 THEN
RETURN 'Salário está ok';
-- Se forem exatos 300 reais, pode aumentar
ELSEIF instrutor.salario = 300 THEN
RETURN 'Salário pode aumentar';
-- Caso contrário, o salário está defasado
ELSE
RETURN 'Salário está defasado';
END IF;
END
$$ LANGUAGE plpgsql;
SELECT nome, salario_ok(instrutor.id) FROM instrutor;
Obs. Um bloco IF
não precisa ter nem um bloco ELSE
nem um bloco ELSEIF
. Ambos são opcionais.
Um bloco IF não precisa necessariamente ser acompanhado por ELSE ou ELSEIF. Inclusive é bastante comum na programação termos somente o IF. Aqui você pode conferir a sintaxe.
O CASE
funciona semelhante ao IF
, contudo é ainda mais legível.
CREATE OR REPLACE FUNCTION salario_ok(id_instrutor INTEGER)
RETURNS VARCHAR AS $$
DECLARE
instrutor instrutor;
BEGIN
SELECT * FROM instrutor WHERE id = id_instrutor INTO instrutor;
CASE
WHEN instrutor.salario = 100 THEN
RETURN 'Salário muito baixo';
WHEN instrutor.salario = 200 THEN
RETURN 'Salário baixo';
WHEN instrutor.salario = 300 THEN
RETURN 'Salário ok';
ELSE
RETURN 'Salário ótimo';
END CASE;
END
$$ LANGUAGE plpgsql;
SELECT nome, salario_ok(instrutor.id) FROM instrutor;
Para simplificar o case, pode-se utilizar a expressão de comparação ao case e diferenciar o when.
CREATE OR REPLACE FUNCTION salario_ok(id_instrutor INTEGER)
RETURNS VARCHAR AS $$
DECLARE
instrutor instrutor;
BEGIN
SELECT * FROM instrutor WHERE id = id_instrutor INTO instrutor;
CASE instrutor.salario
WHEN 100 THEN
RETURN 'Salário muito baixo';
WHEN 200 THEN
RETURN 'Salário baixo';
WHEN 300 THEN
RETURN 'Salário ok';
ELSE
RETURN 'Salário ótimo';
END CASE;
END
$$ LANGUAGE plpgsql;
SELECT nome, salario_ok(instrutor.id) FROM instrutor;
Quando parece ser mais interessante usar CASE?
Quando precisaríamos de muitos ELSEIFs
Se nós precisarmos de muito blocos ELSEIF, normalmente podemos trocar por um CASE. Se a comparação for de igualdade, usamos o CASE simples. Senão, podemos usar o Searched case. Dá uma olhada aqui pra mais detalhes.
O RETURN NEXT
é utilizado para retornar múltiplas linhas de uma função.
CREATE OR REPLACE FUNCTION tabuada(numero INTEGER)
RETURNS SETOF VARCHAR AS $$
BEGIN
RETURN NEXT numero * 1;
RETURN NEXT numero * 2;
RETURN NEXT numero * 3;
RETURN NEXT numero * 4;
RETURN NEXT numero * 5;
RETURN NEXT numero * 6;
RETURN NEXT numero * 7;
RETURN NEXT numero * 8;
RETURN NEXT numero * 9;
END
$$ LANGUAGE plpgsql;
SELECT tabuada(2);
Para saber mais: SETOF PLpgSQL
Criar um LOOP
faz com que reduza a repetição e o código fique mais legível.
DROP FUNCTION tabuada;
CREATE OR REPLACE FUNCTION tabuada(numero INTEGER)
RETURNS SETOF INTEGER AS $$
DECLARE
-- multiplicador que começa com 1 e vai até 10
multiplicador INTEGER DEFAULT 1;
BEGIN
LOOP
-- multiplica numeros
RETURN NEXT numero * multiplicador;
-- aumenta o multiplicador
multiplicador := multiplicador + 1;
-- finaliza o multiplicador
EXIT WHEN multiplicador = 10;
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT tabuada(3);
Pode-se ainda concatenar strings para mostrar o cálculo, não apenas o resultado dele.
DROP FUNCTION tabuada;
CREATE OR REPLACE FUNCTION tabuada(numero INTEGER)
RETURNS SETOF VARCHAR AS $$
DECLARE
multiplicador INTEGER DEFAULT 1;
BEGIN
LOOP
RETURN NEXT numero || ' x ' || multiplicador || ' = ' || numero * multiplicador;
multiplicador := multiplicador + 1;
EXIT WHEN multiplicador = 10;
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT tabuada(4);
O WHILE
funciona como um contador, finalizando o loop quando alcança o que foi definido no while.
DROP FUNCTION tabuada;
CREATE OR REPLACE FUNCTION tabuada(numero INTEGER)
RETURNS SETOF VARCHAR AS $$
DECLARE
multiplicador INTEGER DEFAULT 1;
BEGIN
WHILE multiplicador < 10 LOOP
RETURN NEXT numero || ' x ' || multiplicador || ' = ' || numero * multiplicador;
multiplicador := multiplicador + 1;
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT tabuada(5);
Qual a principal diferença entre estes LOOP e WHILE … LOOP?
Sem o WHILE nós precisamos utilizar o EXIT para informar a condição de saída dentro do loop.
O WHILE já nos permite definir explicitamente a condição de saída. Sem o WHILE nós precisamos, dentro do loop, executar a instrução EXIT para verificar a condição de saída.
O FOR
assim como o while, funciona como um contador, porém ele também faz o incremento do loop. Fazendo com que não seja mais necessário incrementar o loop de 1 em 1 e, também, não mais necessário declarar a variável.
DROP FUNCTION tabuada;
CREATE OR REPLACE FUNCTION tabuada(numero INTEGER)
RETURNS SETOF VARCHAR AS $$
BEGIN
FOR multiplicador IN 1..9 LOOP
RETURN NEXT numero || ' x ' || multiplicador || ' = ' || numero * multiplicador;
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT tabuada(6);
Com o for é possível realizar um loop, inclusive, com uma query.
CREATE FUNCTION instrutor_com_salario(
OUT nome VARCHAR,
OUT salario_ok VARCHAR
)
RETURNS SETOF record AS $$
DECLARE
instrutor instrutor;
BEGIN
FOR instrutor IN SELECT * FROM instrutor LOOP
nome := instrutor.nome;
salario_ok = salario_ok(instrutor.id);
RETURN NEXT;
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT * FROM instrutor_com_salario();
Estruturas de repetição (do inglês, loops) são muito utilizadas na programação para percorrer listas de valores. Sejam vetores de informações, registros de tabelas, sequências pré-definidas… Em cada caso vamos analisar qual a estrutura ideal de repetição para aplicar.
Para saber mais: Loops
É possível criar funções para popular tabelas, inserindo novos registros ou atualizando os existentes. Para isso, é necessário relacionar a função à uma ou mais tabelas e declarar variáveis e campos. Podendo realizar uma verificação se o item já existe antes de realmente inserir como novo.
CREATE FUNCTION cria_curso(nome_curso VARCHAR, nome_categoria VARCHAR)
RETURNS void AS $$
DECLARE
id_categoria INTEGER;
BEGIN
SELECT id INTO id_categoria FROM categoria
WHERE nome = nome_categoria;
IF NOT FOUND THEN
INSERT INTO categoria (nome) VALUES (nome_categoria)
RETURNING id INTO id_categoria;
END IF;
INSERT INTO curso (nome, categoria_id)
VALUES (nome_curso, id_categoria);
END;
$$ LANGUAGE plpgsql;
SELECT cria_curso('PHP', 'Programação');
SELECT cria_curso('Java', 'Programação');
SELECT * FROM curso;
SELECT * FROM categoria;
O que é essa variável FOUND?
Uma variável que informa se houve algum resultado produzido pela última query executada.
Esta variável é definida automaticamente em toda função em PLpgSQL e começa como FALSE. Nesse link você confere em que situações ela será definida como TRUE.
Para saber mais: Execute
⬆️ Voltar ao topo ⬆️