Aprende SQL · lección gratuita

Lección 44 · Triggers y automatización

Resumen

Un trigger (disparador) es un bloque de SQL que el motor ejecuta automáticamente cuando ocurre un evento sobre una tabla: un INSERT, un UPDATE o un DELETE. No lo invocas tú: se dispara solo. Sirven para tareas que no quieres olvidar nunca: auditoría (registrar quién cambió qué), validación (rechazar datos inválidos antes de guardarlos) y mantener acumulados (totales, contadores, saldos calculados en otra tabla).

Sintaxis / Conceptos

La estructura general de un trigger en SQLite es:

CREATE TRIGGER nombre_trigger
  {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON tabla
  [WHEN condición]
BEGIN
  -- una o varias sentencias terminadas en ;
END;

¿Cuándo uso NEW y cuándo OLD? Depende del evento:

La cláusula WHEN filtra: el cuerpo del trigger solo se ejecuta si la condición es verdadera. Por ejemplo WHEN NEW.precio < OLD.precio dispara la lógica solo cuando hubo una rebaja.

Para abortar una operación inválida se usa RAISE(ABORT, 'mensaje') dentro de un trigger BEFORE. Al ejecutarse, cancela la sentencia, deshace su efecto y devuelve el mensaje de error. Es la forma de imponer reglas de negocio que un simple CHECK no cubre.

Nota: cuando haces DROP TABLE, SQLite borra automáticamente todos los triggers asociados a esa tabla (no quedan huérfanos). Y un consejo: usa los triggers con criterio. Son potentes, pero esconden lógica que no se ve al leer un simple INSERT; un exceso de triggers vuelve difícil entender por qué cambian los datos.

-- Pruébalo: este trigger registra automáticamente cada cambio de saldo.
DROP TRIGGER IF EXISTS trg_audit;
DROP TABLE IF EXISTS cuentas_demo;
DROP TABLE IF EXISTS auditoria_demo;
CREATE TABLE cuentas_demo(id INTEGER PRIMARY KEY, saldo INTEGER);
CREATE TABLE auditoria_demo(id INTEGER PRIMARY KEY, msg TEXT);
CREATE TRIGGER trg_audit AFTER UPDATE ON cuentas_demo
BEGIN
  INSERT INTO auditoria_demo(msg) VALUES('cuenta ' || NEW.id || ': ' || OLD.saldo || ' -> ' || NEW.saldo);
END;
INSERT INTO cuentas_demo VALUES (1,100);
UPDATE cuentas_demo SET saldo = 250 WHERE id = 1;
SELECT msg FROM auditoria_demo;

Ejemplos

Estos ejemplos son editables y ejecutables: pulsa Ejecutar (o selecciona unas líneas para correr solo esa parte) y experimenta cambiando los valores. Cada uno crea sus propias tablas _demo, así que puedes ejecutarlos cuantas veces quieras.

-- 1) AUDITORÍA: registrar cada cambio de precio (UPDATE usa OLD y NEW)
DROP TRIGGER IF EXISTS trg_log_precio;
DROP TABLE IF EXISTS productos_demo;
DROP TABLE IF EXISTS bitacora_demo;
CREATE TABLE productos_demo(id INTEGER PRIMARY KEY, nombre TEXT, precio INTEGER);
CREATE TABLE bitacora_demo(id INTEGER PRIMARY KEY, detalle TEXT);
CREATE TRIGGER trg_log_precio AFTER UPDATE ON productos_demo
BEGIN
  INSERT INTO bitacora_demo(detalle) VALUES(NEW.nombre || ': ' || OLD.precio || ' -> ' || NEW.precio);
END;
INSERT INTO productos_demo VALUES (1,'Router',120),(2,'Switch',80);
UPDATE productos_demo SET precio = 150 WHERE id = 1;
UPDATE productos_demo SET precio = 95 WHERE id = 2;
SELECT * FROM bitacora_demo;
-- 2) VALIDACIÓN: BEFORE INSERT con RAISE(ABORT) para rechazar datos inválidos
DROP TRIGGER IF EXISTS trg_valida_salario;
DROP TABLE IF EXISTS empleados_demo;
CREATE TABLE empleados_demo(id INTEGER PRIMARY KEY, nombre TEXT, salario INTEGER);
CREATE TRIGGER trg_valida_salario BEFORE INSERT ON empleados_demo
  WHEN NEW.salario < 0
BEGIN
  SELECT RAISE(ABORT, 'salario invalido');
END;
INSERT INTO empleados_demo VALUES (1,'Ana',3000),(2,'Beto',2500);
-- Quita el "--" de la siguiente línea y ejecuta para ver cómo el trigger RECHAZA el salario negativo:
-- INSERT INTO empleados_demo VALUES (3,'Caro',-100);
SELECT * FROM empleados_demo;
-- 3) ACUMULADO: mantener un total por pedido en otra tabla (UPSERT con ON CONFLICT)
DROP TRIGGER IF EXISTS trg_suma;
DROP TABLE IF EXISTS lineas_demo;
DROP TABLE IF EXISTS totales_demo;
CREATE TABLE lineas_demo(id INTEGER PRIMARY KEY, pedido INTEGER, importe INTEGER);
CREATE TABLE totales_demo(pedido INTEGER PRIMARY KEY, total INTEGER);
CREATE TRIGGER trg_suma AFTER INSERT ON lineas_demo
BEGIN
  INSERT INTO totales_demo(pedido, total) VALUES(NEW.pedido, NEW.importe)
  ON CONFLICT(pedido) DO UPDATE SET total = total + NEW.importe;
END;
INSERT INTO lineas_demo VALUES (1,10,100),(2,10,50),(3,20,200);
SELECT * FROM totales_demo ORDER BY pedido;
💡 Recuerda la regla de oro de NEW/OLD: INSERT solo tiene NEW, DELETE solo tiene OLD, UPDATE tiene los dos. Si intentas usar OLD en un trigger de INSERT (o NEW en uno de DELETE), SQLite dará error.

Cheatsheet

FormaQué hace
CREATE TRIGGER t AFTER INSERT ON tbl BEGIN ... END;Se dispara tras insertar; usa NEW
CREATE TRIGGER t AFTER UPDATE ON tbl BEGIN ... END;Se dispara tras actualizar; usa OLD y NEW
CREATE TRIGGER t AFTER DELETE ON tbl BEGIN ... END;Se dispara tras borrar; usa OLD
... BEFORE INSERT ON tbl WHEN NEW.x < 0 ...Valida antes de aplicar; WHEN filtra
SELECT RAISE(ABORT, 'msg');Cancela la operación y devuelve error
DROP TRIGGER IF EXISTS t;Elimina el trigger
DROP TABLE tbl;Borra la tabla y sus triggers asociados

---

← Optimización (EXPLAIN QUERY PLAN) y motores de bases de datos

Ver todas las lecciones de Aprende SQL →