Aprende SQL · lección gratuita
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).
INSERT, UPDATE o DELETE sobre una tabla concreta.BEFORE (antes de aplicar el cambio, ideal para validar o abortar) o AFTER (después, ideal para auditar o propagar el cambio).FOR EACH ROW: el cuerpo se ejecuta una vez por cada fila afectada. En SQLite los triggers son siempre por fila (es el único modo soportado).NEW y OLD: pseudo-filas con los valores de la fila afectada. NEW = valores entrantes (los nuevos), OLD = valores previos (los que había).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:
NEW (la fila que entra). No hay OLD.OLD (la fila que desaparece). No hay NEW.OLD es el valor anterior y NEW el nuevo. Es perfecto para registrar transiciones (OLD.precio -> NEW.precio).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;
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 deNEW/OLD: INSERT solo tieneNEW, DELETE solo tieneOLD, UPDATE tiene los dos. Si intentas usarOLDen un trigger de INSERT (oNEWen uno de DELETE), SQLite dará error.
| Forma | Qué 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