A veces los triggers se comportan de forma impredecible, y esto puede estar relacionado con:
- Un error lógico en la lógica de la función asociada al trigger.
- Violación de restricciones de la base de datos (por ejemplo, violación de unicidad o incompatibilidad de tipos de datos).
- Problemas en transacciones, cuando el trigger provoca un rollback de los cambios por un error.
- Recursividad, si el trigger se llama a sí mismo (muchas veces por accidente).
Para evitar estos problemas, PostgreSQL te permite manejar errores dentro de los triggers y sus funciones. Estas herramientas incluyen los bloques EXCEPTION y la sentencia RAISE, que vamos a ver hoy con ejemplos.
Manejo de errores usando el bloque EXCEPTION
El bloque EXCEPTION nos permite capturar errores y ejecutar algo de código para manejarlos. Es parecido a usar try-catch en lenguajes como Python o Java.
El bloque EXCEPTION se usa en funciones PL/pgSQL así:
BEGIN
-- Código principal de la función
EXCEPTION
WHEN <tipo_de_error> THEN
-- Código para manejar el error
END;
Donde <tipo_de_error> es el error concreto o grupo de errores que quieres manejar (por ejemplo, unique_violation, division_by_zero, etc.).
Ejemplo: registrar errores en triggers
Imagina que tenemos una tabla logs donde queremos guardar los errores que ocurren al insertar datos en la tabla students. Así sería:
Creamos la tabla para logs
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT
);
Creamos una función con manejo de errores
CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
-- Intentamos ejecutar el código principal
BEGIN
-- Ejemplo de acción "errónea": división por 0
PERFORM 1 / (NEW.some_value - NEW.some_value);
EXCEPTION
WHEN division_by_zero THEN
-- Si ocurre un error de división por 0, lo guardamos en logs
INSERT INTO logs (error_message) VALUES ('Error de división por 0 al insertar en students');
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Creamos el trigger
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();
Ahora, si al insertar datos en la tabla students ocurre un error de división por cero, será manejado y la info se guardará en la tabla logs.
Usando RAISE para diagnóstico y debug
La sentencia RAISE te permite mostrar mensajes de advertencia, error o info para debug. Es una herramienta súper útil cuando quieres entender cómo funciona (¡o no funciona!) tu trigger.
Tipos de mensajes RAISE:
DEBUG— mensaje para debug.NOTICE— mensaje informativo normal.WARNING— advertencia.EXCEPTION— mensaje de error que termina la función.
Sintaxis de RAISE:
RAISE <tipo_de_mensaje> 'Mensaje';
También puedes pasar valores de variables:
RAISE NOTICE 'Valor de NEW.id = %', NEW.id;
Ejemplo: debug de valores en un trigger
Supón que tienes un error al actualizar la tabla students y quieres saber qué valores de NEW y OLD están causando el problema. Para eso usamos RAISE:
CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;
-- Ejemplo de condición que lanza un error:
IF NEW.some_field IS NULL THEN
RAISE EXCEPTION 'El campo some_field no puede ser NULL';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();
Ahora, cada vez que actualices un registro, vas a ver los valores de OLD y NEW, y también un mensaje de error claro si ocurre.
Transacciones en triggers
Los triggers se ejecutan en el contexto de una transacción. Eso significa que si ocurre un error en cualquier parte del trigger o su función, toda la transacción se revierte. Esto protege la base de datos de cambios parciales.
Pero este comportamiento a veces trae problemas:
- Si el error dentro del trigger está relacionado con datos incorrectos, a veces solo quieres revertir parte de las acciones.
- Hay que entender que el rollback de la transacción incluye no solo el trigger, sino toda la operación que lo disparó.
Ejemplo: uso de transacciones en un trigger
Para ilustrar, imagina que queremos ejecutar cierta lógica de negocio que incluye dos operaciones: actualizar la tabla students y guardar un log en logs. Si una de estas operaciones falla, toda la transacción se revierte.
CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
-- Registrar intento de actualización
INSERT INTO logs (error_message) VALUES ('Intento de actualización de estudiante con id ' || NEW.id);
-- Comprobamos condiciones de negocio
IF NEW.some_value IS NULL THEN
RAISE EXCEPTION 'El campo some_value no puede ser NULL';
END IF;
-- Si todo va bien, devolvemos NEW
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();
Errores típicos al trabajar con triggers y cómo prevenirlos
Errores comunes de los desarrolladores:
Triggers recursivos. Esto pasa cuando el trigger hace cambios que lo vuelven a disparar. Solución: usar la condición WHEN o añadir un flag para evitar llamadas repetidas.
Rollback de toda la transacción por errores. Muchas veces no es lo que quieres si el trigger no está directamente relacionado con los datos principales. Solución: usar bien los bloques EXCEPTION.
Demasiada info de debug. Llena los logs y dificulta el análisis. Solución: usa RAISE solo durante desarrollo y pruebas.
Baja de rendimiento. Triggers complejos pueden ralentizar operaciones INSERT, UPDATE o DELETE. Solución: minimiza la lógica del trigger y evita consultas pesadas.
GO TO FULL VERSION