Triggers: automatizando ações no banco
1. O que são Triggers e por que usá-los?
Triggers (ou gatilhos) são procedimentos armazenados no banco de dados que são executados automaticamente em resposta a eventos específicos ocorridos em uma tabela ou visão. Eles representam uma poderosa ferramenta para garantir a integridade dos dados, implementar regras de negócio no nível do banco e automatizar tarefas como auditoria e sincronização.
Os principais benefícios dos triggers incluem:
- Consistência de dados: regras de negócio são aplicadas independentemente da aplicação cliente
- Auditoria automática: registro de alterações sem necessidade de código na aplicação
- Centralização da lógica: regras ficam no banco, acessíveis a todos os sistemas
É importante diferenciar triggers de constraints. Enquanto constraints como CHECK e UNIQUE impõem restrições simples e declarativas, triggers permitem lógica procedural complexa, acesso a múltiplas tabelas e validações que dependem de estado externo.
2. Anatomia de uma Trigger no PostgreSQL
No PostgreSQL, um trigger é composto por duas partes: a trigger function (função que contém a lógica) e o trigger propriamente dito (que associa a função a um evento).
-- Primeiro, criamos a trigger function
CREATE OR REPLACE FUNCTION log_insercao()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO log_auditoria(tabela, operacao, dados, usuario, data_hora)
VALUES ('clientes', 'INSERT', row_to_json(NEW), current_user, NOW());
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Depois, criamos o trigger que chama a função
CREATE TRIGGER trigger_log_insert
AFTER INSERT ON clientes
FOR EACH ROW
EXECUTE FUNCTION log_insercao();
A trigger function deve retornar um tipo TRIGGER e pode acessar variáveis especiais como NEW e OLD. O trigger em si especifica o momento (BEFORE, AFTER, INSTEAD OF), o evento (INSERT, UPDATE, DELETE, TRUNCATE) e o escopo (FOR EACH ROW ou FOR EACH STATEMENT).
3. Tipos de Triggers: Eventos e Tempo de Disparo
Os triggers podem ser classificados pelo momento de disparo e pela operação que os ativa:
Quanto ao momento:
- BEFORE: executado antes da operação, útil para validações e modificações preventivas
- AFTER: executado após a operação, ideal para auditoria e ações que dependem do resultado
- INSTEAD OF: usado em views para substituir a operação padrão
Quanto à operação:
- INSERT: quando uma nova linha é inserida
- UPDATE: quando uma linha existente é modificada
- DELETE: quando uma linha é removida
- TRUNCATE: quando a tabela é truncada
-- Trigger BEFORE para validação
CREATE TRIGGER valida_email
BEFORE INSERT OR UPDATE ON usuarios
FOR EACH ROW
EXECUTE FUNCTION verificar_email_unico();
-- Trigger AFTER para auditoria
CREATE TRIGGER auditoria_delete
AFTER DELETE ON pedidos
FOR EACH ROW
EXECUTE FUNCTION registrar_exclusao();
4. Escopo: FOR EACH ROW vs FOR EACH STATEMENT
A escolha entre FOR EACH ROW e FOR EACH STATEMENT impacta diretamente a performance e o comportamento:
FOR EACH ROW: dispara uma vez para cada linha afetada pelo comando SQL. Ideal para validações linha a linha e operações que dependem de valores específicos.
-- Dispara para cada linha inserida
CREATE TRIGGER valida_estoque
BEFORE INSERT ON itens_pedido
FOR EACH ROW
EXECUTE FUNCTION verificar_disponibilidade();
FOR EACH STATEMENT: dispara uma única vez por comando SQL, independentemente do número de linhas afetadas. Útil para auditoria agregada e operações que não dependem de dados específicos.
-- Dispara uma vez, mesmo que 1000 linhas sejam deletadas
CREATE TRIGGER auditoria_massiva
AFTER DELETE ON pedidos
FOR EACH STATEMENT
EXECUTE FUNCTION registrar_operacao_em_lote();
5. Acessando Dados Dentro da Trigger Function
As variáveis especiais NEW e OLD permitem acessar os valores das linhas envolvidas na operação:
- NEW: contém os novos valores (disponível em
INSERTeUPDATE) - OLD: contém os valores antigos (disponível em
UPDATEeDELETE)
CREATE OR REPLACE FUNCTION impede_reducao_salario()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.salario < OLD.salario THEN
RAISE EXCEPTION 'Não é permitido reduzir o salário de % para %',
OLD.salario, NEW.salario;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_salario
BEFORE UPDATE ON funcionarios
FOR EACH ROW
EXECUTE FUNCTION impede_reducao_salario();
6. Casos de Uso Práticos
Auditoria automática
CREATE TABLE log_auditoria (
id SERIAL PRIMARY KEY,
tabela TEXT,
operacao TEXT,
dados_antigos JSONB,
dados_novos JSONB,
usuario TEXT,
data_hora TIMESTAMP DEFAULT NOW()
);
CREATE OR REPLACE FUNCTION auditoria_geral()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO log_auditoria(tabela, operacao, dados_novos, usuario)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(NEW), current_user);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO log_auditoria(tabela, operacao, dados_antigos, dados_novos, usuario)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW), current_user);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO log_auditoria(tabela, operacao, dados_antigos, usuario)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), current_user);
RETURN OLD;
END IF;
END;
$$ LANGUAGE plpgsql;
Atualização automática de updated_at
CREATE OR REPLACE FUNCTION atualiza_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_updated_at
BEFORE UPDATE ON clientes
FOR EACH ROW
EXECUTE FUNCTION atualiza_updated_at();
7. Cuidados, Boas Práticas e Performance
Evite triggers recursivos: um trigger que modifica a mesma tabela que o disparou pode criar loops infinitos. Use variáveis de sessão ou flags para controlar isso.
-- Exemplo de proteção contra recursão
CREATE OR REPLACE FUNCTION atualiza_total_pedido()
RETURNS TRIGGER AS $$
BEGIN
IF current_setting('meu_app.evitar_recursao', TRUE) = 'true' THEN
RETURN NEW;
END IF;
PERFORM set_config('meu_app.evitar_recursao', 'true', true);
UPDATE pedidos SET total = (SELECT SUM(valor) FROM itens WHERE pedido_id = NEW.pedido_id)
WHERE id = NEW.pedido_id;
PERFORM set_config('meu_app.evitar_recursao', 'false', true);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Impacto em operações em lote: triggers FOR EACH ROW em operações que afetam milhões de linhas podem degradar severamente a performance. Considere usar FOR EACH STATEMENT ou desabilitar temporariamente o trigger.
Documentação: triggers podem tornar o comportamento do banco "invisível" para desenvolvedores. Mantenha uma documentação clara e nomeie triggers de forma descritiva.
Debugging: use RAISE NOTICE para depuração temporária:
RAISE NOTICE 'Trigger disparado para % na tabela %', TG_OP, TG_TABLE_NAME;
8. Limitações e Alternativas
Limitações:
- Triggers não podem retornar dados diretamente ao cliente
- Podem tornar o banco mais complexo e difícil de depurar
- Não são portáveis entre diferentes SGBDs
Alternativas:
- Constraints: para validações simples e declarativas
- Regras de negócio na aplicação: mais flexíveis e testáveis
- Views materializadas: para sincronização de dados agregados
- Event triggers: para capturar eventos no nível do banco (DDL)
Quando evitar:
- Operações de alta frequência onde performance é crítica
- Lógica complexa que seria melhor implementada na aplicação
- Cenários onde a portabilidade entre bancos é essencial
Triggers são ferramentas poderosas quando usadas com moderação e planejamento. Eles brilham em cenários de auditoria, validações complexas e sincronização automática, mas devem ser evitados quando a lógica pode ser implementada de forma mais simples e eficiente em outros níveis da aplicação.
Referências
- Documentação Oficial do PostgreSQL sobre Triggers — Documentação completa sobre criação e uso de triggers no PostgreSQL, incluindo sintaxe e exemplos.
- MySQL Trigger Documentation — Guia oficial do MySQL sobre triggers, com sintaxe e limitações específicas do SGBD.
- SQL Server Triggers (Microsoft) — Documentação da Microsoft sobre triggers DML e DDL no SQL Server.
- Oracle Database Triggers — Referência completa sobre triggers no Oracle, incluindo triggers compostos e mutantes.
- PostgreSQL Trigger Functions and Examples — Tutorial prático com exemplos reais de triggers no PostgreSQL, abordando casos de auditoria e validação.
- Understanding SQL Triggers: A Comprehensive Guide — Artigo técnico do freeCodeCamp explicando conceitos de triggers com exemplos em múltiplos SGBDs.