CREATE TABLE IF NOT EXISTS log_salario (
id_log SERIAL PRIMARY KEY,
id_prof INT NOT NULL,
salario_anterior NUMERIC(10,2),
salario_novo NUMERIC(10,2),
alterado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Triggers
Um trigger é uma instrução executada automaticamente pelo SGBD como efeito colateral de uma modificação no banco de dados. Para projetar um trigger, é preciso especificar:
- O evento que dispara o trigger (
INSERT,UPDATEouDELETE) - O momento em que ele executa (
BEFOREouAFTERo evento) - A ação a ser tomada (um procedimento PL/pgSQL)
Triggers foram introduzidos no padrão SQL:1999 e são amplamente suportados — embora a sintaxe varie entre SGBDs. Os exemplos aqui usam a sintaxe do PostgreSQL.
Estrutura de um Trigger no PostgreSQL
No PostgreSQL, um trigger é sempre associado a uma função de trigger (que retorna TRIGGER):
-- 1. Cria a função que implementa a ação
CREATE OR REPLACE FUNCTION nome_funcao()
RETURNS TRIGGER AS $$
BEGIN
-- lógica usando NEW e/ou OLD
RETURN NEW; -- para BEFORE; AFTER pode retornar NULL
END;
$$ LANGUAGE plpgsql;
-- 2. Associa a função ao evento na tabela
CREATE TRIGGER nome_trigger
{ BEFORE | AFTER } { INSERT | UPDATE | DELETE } [OF coluna, ...]
ON tabela
FOR EACH { ROW | STATEMENT }
EXECUTE FUNCTION nome_funcao();| Variável especial | Disponível em | Conteúdo |
|---|---|---|
NEW |
INSERT, UPDATE |
Linha nova (após a operação) |
OLD |
UPDATE, DELETE |
Linha antiga (antes da operação) |
Em triggers BEFORE, alterar NEW antes de RETURN NEW modifica o valor que será gravado.
BEFORE vs. AFTER
BEFORE |
AFTER |
|
|---|---|---|
| Quando executa | Antes da modificação | Depois que a linha foi alterada |
| Pode cancelar a operação | Sim (retornando NULL) |
Não |
| Uso típico | Validação, derivação de valores | Auditoria, propagação a outras tabelas |
FOR EACH ROW vs. FOR EACH STATEMENT
FOR EACH ROW |
FOR EACH STATEMENT |
|
|---|---|---|
| Execuções por instrução | Uma por linha afetada | Uma por instrução SQL |
Acesso a NEW/OLD |
Sim | Não (usa referencing new/old table) |
| Uso típico | Auditoria por linha, derivações | Totalizadores pós-lote |
Exemplo 1 — AFTER UPDATE: Auditoria de Salários
O trigger trg_log_salario registra toda alteração de salário de professores em uma tabela de log.
CREATE OR REPLACE FUNCTION fn_log_salario()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.salario <> OLD.salario THEN
INSERT INTO log_salario (id_prof, salario_anterior, salario_novo)
VALUES (OLD.id_prof, OLD.salario, NEW.salario);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;DROP TRIGGER IF EXISTS trg_log_salario ON professor;CREATE TRIGGER trg_log_salario
AFTER UPDATE OF salario ON professor
FOR EACH ROW
EXECUTE FUNCTION fn_log_salario();Demonstração
-- Limpa entradas de demos anteriores
TRUNCATE log_salario RESTART IDENTITY;-- Salário original de Gustavo Costa (31200001)
SELECT nome, salario FROM professor WHERE id_prof = 31200001;| nome | salario |
|---|---|
| Gustavo Costa | 3300 |
-- Aplica reajuste de 10% — trigger deve registrar a mudança
UPDATE professor SET salario = 3630.00 WHERE id_prof = 31200001;-- Log registrado pelo trigger
SELECT id_prof, salario_anterior, salario_novo, alterado_em
FROM log_salario
ORDER BY id_log;| id_prof | salario_anterior | salario_novo | alterado_em |
|---|---|---|---|
| 31200001 | 3300 | 3630 | 2026-05-15 18:26:00 |
-- Restaura o salário original
UPDATE professor SET salario = 3300.00 WHERE id_prof = 31200001;-- Log após restauração: dois registros (aumento e restauração)
SELECT id_prof, salario_anterior, salario_novo
FROM log_salario
ORDER BY id_log;| id_prof | salario_anterior | salario_novo |
|---|---|---|
| 31200001 | 3300 | 3630 |
| 31200001 | 3630 | 3300 |
O trigger disparou automaticamente nas duas atualizações — sem nenhuma ação explícita do código que executou o UPDATE.
Exemplo 2 — BEFORE INSERT OR UPDATE: Derivando aprovado da Nota
O campo aprovado em matricula_disciplina pode ser computado automaticamente a partir de nota, eliminando a necessidade de a aplicação calcular e inserir esse valor.
CREATE OR REPLACE FUNCTION fn_calcular_aprovado()
RETURNS TRIGGER AS $$
BEGIN
-- Só deriva aprovado se nota foi fornecida
IF NEW.nota IS NOT NULL THEN
NEW.aprovado := CASE WHEN NEW.nota >= 6.0 THEN 1 ELSE 0 END;
END IF;
RETURN NEW; -- BEFORE: RETURN NEW grava o valor modificado
END;
$$ LANGUAGE plpgsql;DROP TRIGGER IF EXISTS trg_aprovado ON matricula_disciplina;CREATE TRIGGER trg_aprovado
BEFORE INSERT OR UPDATE OF nota ON matricula_disciplina
FOR EACH ROW
EXECUTE FUNCTION fn_calcular_aprovado();Com este trigger ativo, uma instrução como:
INSERT INTO matricula_disciplina
(id_disciplina, ano, semestre, turma, id_aluno, nota)
VALUES (3110401, 2026, 2, 1, 2025311001, 7.5);
-- aprovado será automaticamente definido como 1 pelo trigger…insere a linha com aprovado = 1 sem que o código da aplicação precise calcular esse valor.
Teste este trigger no psql ou DBeaver, inserindo uma linha com nota = 4.5 e verificando que aprovado fica 0, ou com nota = 8.0 para aprovado = 1.
Listando Triggers do Banco
SELECT trigger_name AS trigger,
event_object_table AS tabela,
event_manipulation AS evento,
action_timing AS momento,
action_orientation AS nivel
FROM information_schema.triggers
WHERE trigger_schema = 'public'
ORDER BY tabela, trigger_name, evento;| trigger | tabela | evento | momento | nivel |
|---|---|---|---|---|
| trg_aprovado | matricula_disciplina | INSERT | BEFORE | ROW |
| trg_aprovado | matricula_disciplina | UPDATE | BEFORE | ROW |
| trg_log_salario | professor | UPDATE | AFTER | ROW |
Quando Não Usar Triggers
Triggers são poderosos, mas podem tornar o sistema difícil de depurar e manter:
- Efeitos ocultos: quem escreve um
UPDATEpode não saber que um trigger será disparado. - Cascata inesperada: um trigger pode disparar outros triggers, criando cadeias difíceis de rastrear.
- Performance: triggers
FOR EACH ROWem operações em lote (carga de milhares de linhas) podem ser lentos.
Prefira triggers quando a regra é invariante e deve ser aplicada independente de qual aplicação acessa o banco. Para lógica específica de uma aplicação, mantenha-a na camada de aplicação.
Para Praticar
-- Verificar se o log capturou corretamente as mudanças de salário
SELECT l.id_prof,
p.nome,
l.salario_anterior,
l.salario_novo,
l.salario_novo - l.salario_anterior AS diferenca
FROM log_salario l
JOIN professor p ON p.id_prof = l.id_prof
ORDER BY l.id_log;| id_prof | nome | salario_anterior | salario_novo | diferenca |
|---|---|---|---|---|
| 31200001 | Gustavo Costa | 3300 | 3630 | 330 |
| 31200001 | Gustavo Costa | 3630 | 3300 | -330 |
-- Exercício: criar um trigger que impede que o salário seja reduzido
CREATE OR REPLACE FUNCTION fn_proteger_salario()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.salario < OLD.salario THEN
RAISE EXCEPTION 'Redução de salário não permitida: % → %',
OLD.salario, NEW.salario;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_proteger_salario
BEFORE UPDATE OF salario ON professor
FOR EACH ROW
EXECUTE FUNCTION fn_proteger_salario();
-- Teste: tente reduzir o salário de um professor — deve falhar
UPDATE professor SET salario = 1000.00 WHERE id_prof = 31400002;