Triggers no PostgreSQL não só disparam funções quando alguma coisa acontece, mas também passam umas variáveis mágicas pra essas funções. É por causa dessas variáveis que dá pra saber como estavam os dados na tabela antes da operação, como ficaram depois e até que operação rolou.
OLD— guarda os dados antigos da linha da tabela antes da operação. Usado em triggers praUPDATEeDELETE, porque emINSERTnão tem nada "antigo" mesmo.NEW— guarda os dados novos da linha da tabela depois da operação. Usado em triggers praINSERTeUPDATE.TG_OP— traz a informação em texto sobre qual operação tá rolando:INSERT,UPDATEouDELETE.
Todas essas variáveis já ficam disponíveis automaticamente dentro da função ligada ao trigger.
Teoria sem prática é tipo SQL sem index: lento e triste. Então bora ver exemplos práticos.
Usando OLD pra acessar dados antigos
Imagina que a gente tem uma tabela students. E alguém resolve corrigir a idade de um estudante (vai que bateu a dúvida se o estudante tem 20 anos ou já fez 25).
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
Pra rastrear o que mudou, vamos criar uma tabela de logs:
CREATE TABLE student_changes (
change_id SERIAL PRIMARY KEY,
student_id INT NOT NULL,
old_value INT,
new_value INT,
change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Agora vamos criar uma função que vai registrar as mudanças. É aqui que o OLD brilha:
CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
-- Logando mudança de idade
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
E agora criamos o trigger:
CREATE TRIGGER student_age_update
AFTER UPDATE OF age ON students
FOR EACH ROW
WHEN (OLD.age IS DISTINCT FROM NEW.age) -- Só roda se a idade mudou
EXECUTE FUNCTION log_student_changes();
Bora adicionar um estudante e depois mudar a idade dele:
INSERT INTO students (name, age) VALUES ('Alice', 20);
UPDATE students
SET age = 25
WHERE name = 'Alice';
-- Conferindo o log de mudanças:
SELECT * FROM student_changes;
Você vai ver que o log registrou a mudança: idade de 20 pra 25. Mágica? Não, é o OLD mesmo.
Usando NEW pra dados novos
Agora imagina que a gente quer, ao adicionar um novo estudante, já registrar o ID e o nome dele na tabela de logs (meio paranoico, mas às vezes é útil):
CREATE OR REPLACE FUNCTION log_new_student()
RETURNS TRIGGER AS $$
BEGIN
-- Logando dados do novo estudante
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age); -- Não tem valor antigo, porque é INSERT
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER student_insert_log
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_new_student();
De novo, vamos adicionar um estudante novo e conferir:
INSERT INTO students (name, age) VALUES ('Bob', 22);
-- Conferindo o log:
SELECT * FROM student_changes;
Você vai ver que o novo estudante apareceu no log. Isso sim é cuidar dos dados de verdade!
Usando TG_OP pra saber o tipo da operação
Mas e se a gente quiser um trigger universal pra logar tudo, que funcione pra INSERT, UPDATE e até DELETE? É aí que entra a variável TG_OP.
Vamos criar uma função universal:
CREATE OR REPLACE FUNCTION log_all_operations()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age);
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NULL);
END IF;
RETURN NULL; -- Pra AFTER trigger em DELETE, retorna NULL
END;
$$ LANGUAGE plpgsql;
Agora criamos um trigger que dispara pra todas as três operações:
CREATE TRIGGER universal_student_log
AFTER INSERT OR UPDATE OR DELETE ON students
FOR EACH ROW
EXECUTE FUNCTION log_all_operations();
Vamos adicionar, alterar e deletar um estudante:
INSERT INTO students (name, age) VALUES ('Charlie', 30);
UPDATE students SET age = 31 WHERE name = 'Charlie';
DELETE FROM students WHERE name = 'Charlie';
-- Conferindo o log:
SELECT * FROM student_changes;
Você vai conseguir ver o log de todas as operações — um trigger pra governar todos eles!
Erros comuns ao usar OLD, NEW, TG_OP
Quando você mexe com triggers, pode trombar com alguns problemas clássicos:
"Por que OLD não funciona no insert?" Isso é o padrão: pra INSERT não tem dado antigo. Usa o NEW.
"O que fazer se NEW não funciona no delete?" De novo, é esperado: pra DELETE não tem dado novo. Usa o OLD.
A lógica do trigger entra em recursão infinita. Confere se o trigger não tá chamando ele mesmo sem querer. Pra isso, coloca condições claras no bloco WHEN ou checa o TG_OP.
GO TO FULL VERSION