Los triggers en PostgreSQL no solo permiten lanzar funciones en respuesta a ciertas acciones, sino que también pasan unas variables mágicas a esas funciones. Gracias a estas variables puedes saber qué datos había en la tabla antes de la operación, qué quedó después y qué operación se realizó realmente.
OLD— contiene los datos antiguos de la fila de la tabla antes de la operación. Se usa en triggers paraUPDATEyDELETE, porque en el caso deINSERTsimplemente no existe nada "viejo".NEW— contiene los datos nuevos de la fila de la tabla después de la operación. Se usa en triggers paraINSERTyUPDATE.TG_OP— contiene información en texto sobre la operación actual:INSERT,UPDATEoDELETE.
Todas estas variables están disponibles automáticamente dentro de la función asociada al trigger.
La teoría sin práctica es como SQL sin índices: lento y triste. Así que vamos a verlo con ejemplos prácticos.
Uso de OLD para acceder a los datos antiguos
Imagina que tenemos una tabla students. Y alguien corrige la edad de un estudiante (quizá se le cruzaron los cables y pensó que el estudiante aún tiene 20 años, y no ya 25).
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
Para rastrear qué cambios ocurrieron, vamos a crear una tabla 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
);
Ahora vamos más allá: creamos una función que va a registrar los cambios. Aquí es donde OLD nos viene de perlas:
CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
-- Registramos los cambios de edad
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Y ahora creamos el trigger:
CREATE TRIGGER student_age_update
AFTER UPDATE OF age ON students
FOR EACH ROW
WHEN (OLD.age IS DISTINCT FROM NEW.age) -- Solo si la edad cambió
EXECUTE FUNCTION log_student_changes();
Vamos a añadir un estudiante y luego modificarlo:
INSERT INTO students (name, age) VALUES ('Alicia', 20);
UPDATE students
SET age = 25
WHERE name = 'Alicia';
-- Revisamos el log de cambios:
SELECT * FROM student_changes;
Vas a ver que en la tabla de logs se registró el cambio: edad de 20 a 25. ¿Magia? No, OLD.
Uso de NEW para los datos nuevos
Ahora imagina que queremos, al añadir un nuevo estudiante, registrar automáticamente su ID y nombre en la tabla de logs (sí, es un poco paranoico, pero a veces útil):
CREATE OR REPLACE FUNCTION log_new_student()
RETURNS TRIGGER AS $$
BEGIN
-- Registramos los datos del nuevo estudiante
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age); -- No hay valor antiguo, porque es un INSERT
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER student_insert_log
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_new_student();
De nuevo, añadimos un nuevo estudiante y revisamos:
INSERT INTO students (name, age) VALUES ('Bob', 22);
-- Revisamos el log:
SELECT * FROM student_changes;
Verás que en los logs aparece el nuevo estudiante. ¡Eso sí que es cuidar los datos a otro nivel!
Uso de TG_OP para saber el tipo de operación
Pero, ¿y si queremos tener un trigger universal para registrar logs, que pueda manejar INSERT, UPDATE e incluso DELETE? Aquí es donde la variable TG_OP nos salva.
Vamos a crear una función 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; -- Para triggers AFTER en DELETE devolvemos NULL
END;
$$ LANGUAGE plpgsql;
Creamos un trigger que se dispare en las tres operaciones:
CREATE TRIGGER universal_student_log
AFTER INSERT OR UPDATE OR DELETE ON students
FOR EACH ROW
EXECUTE FUNCTION log_all_operations();
Añadimos, modificamos y borramos un estudiante:
INSERT INTO students (name, age) VALUES ('Charlie', 30);
UPDATE students SET age = 31 WHERE name = 'Charlie';
DELETE FROM students WHERE name = 'Charlie';
-- Revisamos el log:
SELECT * FROM student_changes;
Puedes ver el log de todas las operaciones — ¡un trigger para gobernarlos a todos!
Errores típicos al usar OLD, NEW, TG_OP
Al trabajar con triggers, puedes encontrarte con algunos problemas comunes:
"¿Por qué OLD no funciona en un insert?" Es el comportamiento por defecto: para INSERT no hay datos antiguos. Usa NEW.
"¿Qué hago si NEW no funciona en un delete?" De nuevo, es lo esperado: para DELETE no hay datos nuevos. Usa OLD.
La lógica del trigger causa recursión infinita. Asegúrate de que el trigger no se llame a sí mismo por accidente. Para eso puedes poner condiciones claras en el bloque WHEN o comprobar TG_OP.
GO TO FULL VERSION