Stored procedures e functions no PostgreSQL
1. Introdução às Stored Procedures e Functions
Stored procedures e functions são blocos de código armazenados no banco de dados que encapsulam lógica de negócio diretamente no PostgreSQL. Embora ambos ofereçam reutilização e desempenho, existem diferenças conceituais fundamentais.
Uma function sempre retorna um valor (ou um conjunto) e pode ser usada em expressões SQL, como em cláusulas SELECT, WHERE ou JOIN. Uma procedure foi introduzida no PostgreSQL 11 e não exige retorno, sendo ideal para operações que envolvem transações, DML sem retorno ou chamadas que precisam de controle transacional explícito.
As vantagens são significativas: redução de tráfego entre aplicação e banco, centralização da lógica, maior segurança (usuários executam procedures sem acesso direto às tabelas) e desempenho superior, pois o código é compilado e otimizado pelo planner do PostgreSQL.
2. Sintaxe Básica e Estrutura
Bloco CREATE FUNCTION
CREATE OR REPLACE FUNCTION calcular_desconto(valor NUMERIC, percentual NUMERIC)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
BEGIN
RETURN valor * (1 - percentual / 100);
END;
$$;
Bloco CREATE PROCEDURE
CREATE OR REPLACE PROCEDURE atualizar_estoque(produto_id INT, quantidade INT)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE produtos SET estoque = estoque - quantidade WHERE id = produto_id;
COMMIT;
END;
$$;
Parâmetros IN, OUT e INOUT
CREATE OR REPLACE FUNCTION dividir_numeros(
IN a NUMERIC,
IN b NUMERIC,
OUT quociente NUMERIC,
OUT resto NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
quociente := a / b;
resto := a % b;
END;
$$;
Retorno com RETURNS TABLE
CREATE OR REPLACE FUNCTION clientes_por_cidade(cidade_nome TEXT)
RETURNS TABLE(id INT, nome TEXT, email TEXT)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY SELECT id, nome, email FROM clientes WHERE cidade = cidade_nome;
END;
$$;
3. Controle de Fluxo e Variáveis
Declaração e Condicionais
CREATE OR REPLACE FUNCTION classificar_produto(preco NUMERIC)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
categoria TEXT;
BEGIN
IF preco < 50 THEN
categoria := 'Econômico';
ELSIF preco BETWEEN 50 AND 200 THEN
categoria := 'Padrão';
ELSE
categoria := 'Premium';
END IF;
RETURN categoria;
END;
$$;
Laços de Repetição
CREATE OR REPLACE FUNCTION gerar_sequencia(inicio INT, fim INT)
RETURNS TABLE(numero INT)
LANGUAGE plpgsql
AS $$
DECLARE
contador INT := inicio;
BEGIN
WHILE contador <= fim LOOP
numero := contador;
RETURN NEXT;
contador := contador + 1;
END LOOP;
END;
$$;
Tratamento de Exceções
CREATE OR REPLACE FUNCTION inserir_cliente_seguro(nome TEXT, email TEXT)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO clientes(nome, email) VALUES (nome, email);
RETURN 'Sucesso';
EXCEPTION
WHEN unique_violation THEN
RETURN 'Email já cadastrado';
WHEN OTHERS THEN
RETURN 'Erro desconhecido: ' || SQLERRM;
END;
$$;
4. Trabalhando com Dados: DML e Cursores
DML dentro de Functions/Procedures
CREATE OR REPLACE PROCEDURE transferir_saldo(
conta_origem INT,
conta_destino INT,
valor NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE contas SET saldo = saldo - valor WHERE id = conta_origem;
UPDATE contas SET saldo = saldo + valor WHERE id = conta_destino;
END;
$$;
Cursores Explícitos
CREATE OR REPLACE FUNCTION processar_pedidos_pendentes()
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
pedido_cursor CURSOR FOR SELECT id, total FROM pedidos WHERE status = 'pendente';
pedido_id INT;
total_pedido NUMERIC;
BEGIN
OPEN pedido_cursor;
LOOP
FETCH pedido_cursor INTO pedido_id, total_pedido;
EXIT WHEN NOT FOUND;
UPDATE pedidos SET status = 'processado' WHERE id = pedido_id;
END LOOP;
CLOSE pedido_cursor;
END;
$$;
Cursores Implícitos com FOR
CREATE OR REPLACE FUNCTION listar_pedidos_por_cliente(cliente_id INT)
RETURNS TABLE(pedido_id INT, data_pedido DATE, valor_total NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
FOR pedido_id, data_pedido, valor_total IN
SELECT id, data, total FROM pedidos WHERE cliente_id = cliente_id
LOOP
RETURN NEXT;
END LOOP;
END;
$$;
5. Transações e Controle de Commit
Em functions, transações são atômicas: ou todo o bloco é executado ou nada é persistido. Procedures permitem controle transacional explícito com COMMIT e ROLLBACK.
CREATE OR REPLACE PROCEDURE processar_lote_com_commit()
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO log_operacoes(descricao) VALUES ('Início do lote');
COMMIT;
-- Operações parciais
UPDATE produtos SET preco = preco * 1.1 WHERE categoria = 'eletrônicos';
COMMIT;
-- Se algo falhar, rollback parcial
BEGIN
INSERT INTO pedidos(cliente_id, total) VALUES (999, 1000);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
INSERT INTO log_erros(mensagem) VALUES ('Falha ao inserir pedido');
COMMIT;
END;
END;
$$;
6. Funções Avançadas: Agregação e Trigger Functions
Função como Gatilho (Trigger Function)
CREATE OR REPLACE FUNCTION auditoria_alteracoes()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO log_auditoria(tabela, acao, dados_antigos, dados_novos, usuario)
VALUES (TG_TABLE_NAME, TG_OP, NULL, row_to_json(NEW), current_user);
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO log_auditoria(tabela, acao, dados_antigos, dados_novos, usuario)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW), current_user);
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO log_auditoria(tabela, acao, dados_antigos, dados_novos, usuario)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), NULL, current_user);
END IF;
RETURN NEW;
END;
$$;
Aplicando o Gatilho
CREATE TRIGGER trg_auditoria_clientes
AFTER INSERT OR UPDATE OR DELETE ON clientes
FOR EACH ROW EXECUTE FUNCTION auditoria_alteracoes();
7. Performance, Manutenção e Segurança
Cache de Planos de Execução
O PostgreSQL compila e armazena em cache o plano de execução de procedures e functions. Para evitar planos obsoletos, use EXPLAIN ANALYZE periodicamente.
EXPLAIN ANALYZE SELECT * FROM clientes_por_cidade('São Paulo');
Segurança com SECURITY DEFINER
CREATE OR REPLACE FUNCTION alterar_senha(usuario_id INT, nova_senha TEXT)
RETURNS BOOLEAN
SECURITY DEFINER
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE usuarios SET senha_hash = crypt(nova_senha, gen_salt('bf')) WHERE id = usuario_id;
RETURN FOUND;
END;
$$;
SECURITY DEFINER executa com privilégios do criador (DBA), permitindo que usuários comuns alterem senhas sem acesso direto à tabela. SECURITY INVOKER (padrão) executa com privilégios do chamador.
Boas Práticas de Manutenção
- Versionamento: use
CREATE OR REPLACE FUNCTIONe mantenha scripts em controle de versão. - Documentação: adicione comentários com
COMMENT ON FUNCTION. - Evite lógica complexa dentro do banco se a aplicação puder fazer de forma mais eficiente.
8. Exemplos Práticos e Casos de Uso
Procedure para Inserção em Lote com Validação
CREATE OR REPLACE PROCEDURE inserir_pedidos_lote(pedidos_json JSONB)
LANGUAGE plpgsql
AS $$
DECLARE
pedido RECORD;
BEGIN
FOR pedido IN SELECT * FROM jsonb_to_recordset(pedidos_json)
AS (cliente_id INT, valor NUMERIC, data_pedido DATE)
LOOP
IF pedido.valor <= 0 THEN
RAISE EXCEPTION 'Valor inválido para pedido do cliente %', pedido.cliente_id;
END IF;
INSERT INTO pedidos(cliente_id, total, data) VALUES (pedido.cliente_id, pedido.valor, pedido.data_pedido);
END LOOP;
COMMIT;
END;
$$;
Function para Cálculo de Totais com Cursor
CREATE OR REPLACE FUNCTION calcular_total_vendas_periodo(data_inicio DATE, data_fim DATE)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
DECLARE
total NUMERIC := 0;
pedido_cursor CURSOR FOR SELECT total FROM pedidos WHERE data BETWEEN data_inicio AND data_fim AND status = 'concluído';
valor_pedido NUMERIC;
BEGIN
OPEN pedido_cursor;
LOOP
FETCH pedido_cursor INTO valor_pedido;
EXIT WHEN NOT FOUND;
total := total + valor_pedido;
END LOOP;
CLOSE pedido_cursor;
RETURN total;
END;
$$;
Comparação: Function vs Procedure vs View Materializada
| Recurso | Function | Procedure | View Materializada |
|---|---|---|---|
| Retorno | Obrigatório | Opcional | Dados armazenados |
| Transações | Atômicas | Controle explícito | Atualização periódica |
| Uso em SELECT | Sim | Não | Sim |
| DML complexo | Limitado | Completo | Limitado |
Escolha function quando precisar de um valor computado em consultas, procedure para operações transacionais complexas, e view materializada para dados agregados que mudam com pouca frequência.
Referências
- Documentação Oficial do PostgreSQL: CREATE FUNCTION — Referência completa sobre sintaxe, parâmetros e opções para criação de funções no PostgreSQL.
- Documentação Oficial do PostgreSQL: CREATE PROCEDURE — Guia oficial sobre procedures, incluindo controle transacional e diferenças para functions.
- PostgreSQL Tutorial: Stored Procedures and Functions — Tutoriais práticos com exemplos passo a passo de procedures e functions.
- PostgreSQL Documentation: PL/pgSQL Control Structures — Detalhamento de estruturas de controle, laços e tratamento de exceções em PL/pgSQL.
- PostgreSQL Documentation: Triggers and Trigger Functions — Guia completo sobre criação de funções para gatilhos, variáveis NEW/OLD e TG_OP.
- PostgreSQL Documentation: Transaction Management — Como gerenciar transações, COMMIT, ROLLBACK e SAVEPOINT dentro de procedures.
- PostgreSQL Documentation: Security with Functions — Explicação sobre SECURITY DEFINER e SECURITY INVOKER para controle de permissões.