CodeGym /Cursos /SQL SELF /Interação de triggers com funções PL/pgSQL: OLD, NEW, TG_...

Interação de triggers com funções PL/pgSQL: OLD, NEW, TG_OP

SQL SELF
Nível 57 , Lição 4
Disponível

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 pra UPDATE e DELETE, porque em INSERT não tem nada "antigo" mesmo.
  • NEW — guarda os dados novos da linha da tabela depois da operação. Usado em triggers pra INSERT e UPDATE.
  • TG_OP — traz a informação em texto sobre qual operação tá rolando: INSERT, UPDATE ou DELETE.

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.

1
Pesquisa/teste
Introdução a triggers, nível 57, lição 4
Indisponível
Introdução a triggers
Introdução a triggers
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION