Salta el contingut

Disparadors (Triggers)

Què és un trigger?

Un trigger (disparador) és un objecte de la base de dades que s'executa automàticament en resposta a un esdeveniment concret sobre una taula o vista: una inserció (INSERT), una modificació (UPDATE) o una eliminació (DELETE). A diferència dels procediments, no es criden explícitament; el SGBD els invoca de manera transparent sempre que es produeix l'esdeveniment que els ha activat.

Els triggers permeten implementar lògica que ha de ser garantida independentment de quin client o aplicació executi l'operació. No importa si la modificació arriba des d'una aplicació web, d'una importació massiva de dades o d'un DBA treballant des de la consola: el trigger s'executa sempre.

sequenceDiagram
    participant APP as Aplicació
    participant SGBD as SGBD
    participant T as Trigger
    participant Tbl as Taula principal
    participant Audit as Taula d'auditoria

    APP->>SGBD: UPDATE productes SET preu=150 WHERE id=7
    SGBD->>T: Activa trigger AFTER UPDATE
    T->>Audit: INSERT INTO audit_productes (...)
    T->>SGBD: Trigger completat
    SGBD->>Tbl: Aplica el UPDATE
    SGBD->>APP: Operació completada

Tipus de triggers

Per moment d'execució: BEFORE i AFTER

  • BEFORE: s'executa abans que l'operació modifiqui les dades. Ideal per a:

    • Validar o modificar els valors nous (NEW) abans que s'escriguin.
    • Rebutjar l'operació si no compleix les regles de negoci.
    • Calcular valors derivats que han de formar part del registre inserit.
  • AFTER: s'executa després que l'operació hagi modificat les dades. Ideal per a:

    • Registrar l'operació en una taula d'auditoria.
    • Actualitzar taules relacionades (totals, estoc...).
    • Enviar notificacions o disparar altres processos.

Per granularitat: FOR EACH ROW i FOR EACH STATEMENT

  • FOR EACH ROW: el trigger s'executa una vegada per cada fila afectada per l'operació. Té accés a OLD i NEW, els valors de la fila concreta. (Tots els motors el suporten.)

  • FOR EACH STATEMENT: el trigger s'executa una vegada per tota l'operació, independentment de quantes files s'hagin modificat. No té accés als valors individuals de cada fila. (PostgreSQL i SQL Server.)

Resum de combinacions

graph TD
    TR[Trigger]
    TR --> MOMENT{Moment}
    TR --> GRAN{Granularitat}

    MOMENT --> BEF[BEFORE]
    MOMENT --> AFT[AFTER]
    MOMENT --> INS[INSTEAD OF\nSQL Server / Oracle / PostgreSQL vistes]

    GRAN --> ROW[FOR EACH ROW\nTots els motors]
    GRAN --> STMT[FOR EACH STATEMENT\nPostgreSQL, SQL Server]

    BEF --> BEF_US[Validació, modificació\ndels valors nous]
    AFT --> AFT_US[Auditoria, actualització\nde taules relacionades]
    INS --> INS_US[Operacions sobre vistes\nnot updatable]

Creació de triggers

Sintaxi bàsica

En PostgreSQL, els triggers criden una funció de trigger separada. En els altres motors, el codi va directament dins del CREATE TRIGGER.

-- Pas 1: Crear la funció de trigger (retorna TRIGGER)
CREATE OR REPLACE FUNCTION trg_fn_exemple()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    -- Lògica del trigger aquí
    -- NEW conté els nous valors (INSERT / UPDATE)
    -- OLD conté els valors anteriors (UPDATE / DELETE)
    RETURN NEW;  -- Obligatori per a BEFORE (retorna la fila a escriure)
                 -- Per a AFTER: RETURN NULL (s'ignora)
END;
$$;

-- Pas 2: Crear el trigger i associar-lo a la taula i funció
CREATE OR REPLACE TRIGGER trg_exemple
BEFORE INSERT OR UPDATE ON productes
FOR EACH ROW
EXECUTE FUNCTION trg_fn_exemple();
-- En MySQL, el codi va directament dins del CREATE TRIGGER
DELIMITER //

CREATE TRIGGER trg_exemple
BEFORE INSERT ON productes
FOR EACH ROW
BEGIN
    -- Lògica del trigger
    -- NEW.columna per accedir al nou valor
    SET NEW.nom = UPPER(NEW.nom);
END //

DELIMITER ;
-- En SQL Server, el codi va dins del CREATE TRIGGER
-- Les taules especials 'inserted' i 'deleted' contenen les files afectades
CREATE OR ALTER TRIGGER trg_exemple
ON productes
AFTER INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    -- 'inserted' conté les noves files (INSERT / UPDATE)
    -- 'deleted' conté les files anteriors (UPDATE / DELETE)
    SELECT * FROM inserted;  -- exemple
END;
GO
-- Oracle: el codi va dins del CREATE TRIGGER
-- :NEW i :OLD (amb dos punts) accedeixen als valors
CREATE OR REPLACE TRIGGER trg_exemple
BEFORE INSERT OR UPDATE ON productes
FOR EACH ROW
BEGIN
    -- :NEW.columna per als nous valors
    -- :OLD.columna per als valors anteriors
    :NEW.nom := UPPER(:NEW.nom);
END trg_exemple;
/

Accés als valors OLD i NEW

-- NEW: valors nous (INSERT i UPDATE)
-- OLD: valors anteriors (UPDATE i DELETE)
-- En un INSERT: OLD no existeix (és NULL)
-- En un DELETE: NEW no existeix (és NULL)

CREATE OR REPLACE FUNCTION trg_fn_mostra_canvis()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        RAISE NOTICE 'INSERT: nou preu = %', NEW.preu;
    ELSIF TG_OP = 'UPDATE' THEN
        RAISE NOTICE 'UPDATE: preu % → %', OLD.preu, NEW.preu;
    ELSIF TG_OP = 'DELETE' THEN
        RAISE NOTICE 'DELETE: preu eliminat = %', OLD.preu;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- NEW.columna: valor nou (INSERT / UPDATE)
-- OLD.columna: valor anterior (UPDATE / DELETE)
DELIMITER //

CREATE TRIGGER trg_mostra_canvis
AFTER UPDATE ON productes
FOR EACH ROW
BEGIN
    IF OLD.preu <> NEW.preu THEN
        INSERT INTO log_preus(producte_id, preu_anterior, preu_nou, data_canvi)
        VALUES (OLD.id, OLD.preu, NEW.preu, NOW());
    END IF;
END //

DELIMITER ;
-- 'inserted': taula virtual amb les files noves (INSERT / UPDATE)
-- 'deleted':  taula virtual amb les files anteriors (UPDATE / DELETE)
-- Poden contenir MÚLTIPLES files si l'operació afecta N files alhora

CREATE OR ALTER TRIGGER trg_mostra_canvis
ON productes
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    INSERT INTO log_preus(producte_id, preu_anterior, preu_nou, data_canvi)
    SELECT
        d.id,
        d.preu,
        i.preu,
        GETDATE()
    FROM deleted d
    JOIN inserted i ON i.id = d.id
    WHERE d.preu <> i.preu;
END;
GO
-- :NEW.columna: valor nou (INSERT / UPDATE)
-- :OLD.columna: valor anterior (UPDATE / DELETE)
-- Nota: :NEW és NULL en DELETE, :OLD és NULL en INSERT

CREATE OR REPLACE TRIGGER trg_mostra_canvis
AFTER UPDATE ON productes
FOR EACH ROW
WHEN (OLD.preu <> NEW.preu)  -- Condició WHEN (sense els dos punts en WHEN)
BEGIN
    INSERT INTO log_preus(producte_id, preu_anterior, preu_nou, data_canvi)
    VALUES (:OLD.id, :OLD.preu, :NEW.preu, SYSDATE);
END trg_mostra_canvis;
/

Exemple 1: Trigger d'auditoria

Volem registrar tots els canvis (INSERT, UPDATE, DELETE) sobre la taula productes en una taula d'auditoria audit_productes.

Creació de la taula d'auditoria

-- Estructura comuna (adaptar tipus de dades al motor)
CREATE TABLE audit_productes (
    id           SERIAL PRIMARY KEY,  -- AUTO_INCREMENT en MySQL; IDENTITY en SQL Server
    operacio     VARCHAR(10),         -- 'INSERT', 'UPDATE', 'DELETE'
    producte_id  INT,
    nom_anterior VARCHAR(200),
    nom_nou      VARCHAR(200),
    preu_anterior DECIMAL(10,2),
    preu_nou      DECIMAL(10,2),
    usuari        VARCHAR(100),
    data_hora     TIMESTAMP
);

Trigger d'auditoria

CREATE OR REPLACE FUNCTION trg_fn_audit_productes()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    INSERT INTO audit_productes (
        operacio, producte_id,
        nom_anterior, nom_nou,
        preu_anterior, preu_nou,
        usuari, data_hora
    )
    VALUES (
        TG_OP,
        COALESCE(NEW.id, OLD.id),
        OLD.nom,  NEW.nom,
        OLD.preu, NEW.preu,
        current_user, NOW()
    );
    RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_audit_productes
AFTER INSERT OR UPDATE OR DELETE ON productes
FOR EACH ROW
EXECUTE FUNCTION trg_fn_audit_productes();
-- MySQL necessita un trigger separat per cada operació DML

DELIMITER //

CREATE TRIGGER trg_audit_productes_insert
AFTER INSERT ON productes
FOR EACH ROW
BEGIN
    INSERT INTO audit_productes (
        operacio, producte_id, nom_nou, preu_nou, usuari, data_hora
    ) VALUES (
        'INSERT', NEW.id, NEW.nom, NEW.preu, USER(), NOW()
    );
END //

CREATE TRIGGER trg_audit_productes_update
AFTER UPDATE ON productes
FOR EACH ROW
BEGIN
    INSERT INTO audit_productes (
        operacio, producte_id,
        nom_anterior, nom_nou,
        preu_anterior, preu_nou,
        usuari, data_hora
    ) VALUES (
        'UPDATE', OLD.id,
        OLD.nom, NEW.nom,
        OLD.preu, NEW.preu,
        USER(), NOW()
    );
END //

CREATE TRIGGER trg_audit_productes_delete
AFTER DELETE ON productes
FOR EACH ROW
BEGIN
    INSERT INTO audit_productes (
        operacio, producte_id, nom_anterior, preu_anterior, usuari, data_hora
    ) VALUES (
        'DELETE', OLD.id, OLD.nom, OLD.preu, USER(), NOW()
    );
END //

DELIMITER ;
CREATE OR ALTER TRIGGER trg_audit_productes
ON productes
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON;

    -- Determinar l'operació
    DECLARE @op NVARCHAR(10);
    IF EXISTS (SELECT 1 FROM inserted) AND EXISTS (SELECT 1 FROM deleted)
        SET @op = 'UPDATE';
    ELSE IF EXISTS (SELECT 1 FROM inserted)
        SET @op = 'INSERT';
    ELSE
        SET @op = 'DELETE';

    -- Insertar un registre per cada fila afectada
    INSERT INTO audit_productes (
        operacio, producte_id,
        nom_anterior, nom_nou,
        preu_anterior, preu_nou,
        usuari, data_hora
    )
    SELECT
        @op,
        COALESCE(i.id, d.id),
        d.nom,  i.nom,
        d.preu, i.preu,
        SYSTEM_USER, GETDATE()
    FROM inserted i
    FULL OUTER JOIN deleted d ON i.id = d.id;
END;
GO
CREATE OR REPLACE TRIGGER trg_audit_productes
AFTER INSERT OR UPDATE OR DELETE ON productes
FOR EACH ROW
BEGIN
    INSERT INTO audit_productes (
        operacio, producte_id,
        nom_anterior, nom_nou,
        preu_anterior, preu_nou,
        usuari, data_hora
    ) VALUES (
        CASE
            WHEN INSERTING THEN 'INSERT'
            WHEN UPDATING  THEN 'UPDATE'
            WHEN DELETING  THEN 'DELETE'
        END,
        COALESCE(:NEW.id, :OLD.id),
        :OLD.nom,  :NEW.nom,
        :OLD.preu, :NEW.preu,
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        SYSTIMESTAMP
    );
END trg_audit_productes;
/

Exemple 2: Trigger de validació (prevenir preus negatius)

CREATE OR REPLACE FUNCTION trg_fn_valida_preu()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    IF NEW.preu < 0 THEN
        RAISE EXCEPTION 'El preu no pot ser negatiu (valor: %)', NEW.preu
            USING ERRCODE = 'check_violation';
    END IF;
    IF NEW.preu = 0 THEN
        RAISE WARNING 'S''ha inserit un producte amb preu zero: %', NEW.nom;
    END IF;
    RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_valida_preu
BEFORE INSERT OR UPDATE OF preu ON productes
FOR EACH ROW
EXECUTE FUNCTION trg_fn_valida_preu();
DELIMITER //

CREATE TRIGGER trg_valida_preu_insert
BEFORE INSERT ON productes
FOR EACH ROW
BEGIN
    IF NEW.preu < 0 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'El preu no pot ser negatiu';
    END IF;
END //

CREATE TRIGGER trg_valida_preu_update
BEFORE UPDATE ON productes
FOR EACH ROW
BEGIN
    IF NEW.preu < 0 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'El preu no pot ser negatiu';
    END IF;
END //

DELIMITER ;
CREATE OR ALTER TRIGGER trg_valida_preu
ON productes
INSTEAD OF INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF EXISTS (SELECT 1 FROM inserted WHERE preu < 0)
    BEGIN
        RAISERROR ('El preu no pot ser negatiu.', 16, 1);
        ROLLBACK TRANSACTION;
        RETURN;
    END;

    -- Si la validació passa, executar l'operació real
    IF EXISTS (SELECT 1 FROM deleted)
        -- Era un UPDATE
        UPDATE p SET preu = i.preu, nom = i.nom
        FROM productes p JOIN inserted i ON i.id = p.id;
    ELSE
        -- Era un INSERT
        INSERT INTO productes SELECT * FROM inserted;
END;
GO
CREATE OR REPLACE TRIGGER trg_valida_preu
BEFORE INSERT OR UPDATE OF preu ON productes
FOR EACH ROW
BEGIN
    IF :NEW.preu < 0 THEN
        RAISE_APPLICATION_ERROR(
            -20010,
            'El preu no pot ser negatiu: ' || :NEW.preu
        );
    END IF;
END trg_valida_preu;
/

Exemple 3: Trigger de camp calculat (actualitzar total de comanda)

Cada vegada que s'insereix o modifica una línia de comanda, actualitzem automàticament el total de la comanda pare.

CREATE OR REPLACE FUNCTION trg_fn_actualitza_total_comanda()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
    v_comanda_id INT;
BEGIN
    -- Determinar quina comanda s'ha vist afectada
    v_comanda_id := COALESCE(NEW.comanda_id, OLD.comanda_id);

    -- Recalcular el total sumant totes les línies
    UPDATE comandes
    SET total = (
        SELECT COALESCE(SUM(quantitat * preu_unitari), 0)
        FROM linies_comanda
        WHERE comanda_id = v_comanda_id
    )
    WHERE id = v_comanda_id;

    RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_actualitza_total
AFTER INSERT OR UPDATE OR DELETE ON linies_comanda
FOR EACH ROW
EXECUTE FUNCTION trg_fn_actualitza_total_comanda();
DELIMITER //

CREATE TRIGGER trg_actualitza_total_insert
AFTER INSERT ON linies_comanda
FOR EACH ROW
BEGIN
    UPDATE comandes
    SET total = (
        SELECT COALESCE(SUM(quantitat * preu_unitari), 0)
        FROM linies_comanda
        WHERE comanda_id = NEW.comanda_id
    )
    WHERE id = NEW.comanda_id;
END //

CREATE TRIGGER trg_actualitza_total_update
AFTER UPDATE ON linies_comanda
FOR EACH ROW
BEGIN
    UPDATE comandes
    SET total = (
        SELECT COALESCE(SUM(quantitat * preu_unitari), 0)
        FROM linies_comanda
        WHERE comanda_id = NEW.comanda_id
    )
    WHERE id = NEW.comanda_id;
END //

CREATE TRIGGER trg_actualitza_total_delete
AFTER DELETE ON linies_comanda
FOR EACH ROW
BEGIN
    UPDATE comandes
    SET total = (
        SELECT COALESCE(SUM(quantitat * preu_unitari), 0)
        FROM linies_comanda
        WHERE comanda_id = OLD.comanda_id
    )
    WHERE id = OLD.comanda_id;
END //

DELIMITER ;
CREATE OR ALTER TRIGGER trg_actualitza_total
ON linies_comanda
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON;

    -- Actualitzar totes les comandes afectades de cop
    UPDATE c
    SET c.total = COALESCE((
        SELECT SUM(lc.quantitat * lc.preu_unitari)
        FROM linies_comanda lc
        WHERE lc.comanda_id = c.id
    ), 0)
    FROM comandes c
    WHERE c.id IN (
        SELECT comanda_id FROM inserted
        UNION
        SELECT comanda_id FROM deleted
    );
END;
GO
CREATE OR REPLACE TRIGGER trg_actualitza_total
AFTER INSERT OR UPDATE OR DELETE ON linies_comanda
FOR EACH ROW
DECLARE
    v_comanda_id NUMBER;
BEGIN
    v_comanda_id := COALESCE(:NEW.comanda_id, :OLD.comanda_id);

    UPDATE comandes
    SET total = (
        SELECT NVL(SUM(quantitat * preu_unitari), 0)
        FROM linies_comanda
        WHERE comanda_id = v_comanda_id
    )
    WHERE id = v_comanda_id;
END trg_actualitza_total;
/

INSTEAD OF triggers (per a vistes)

Els triggers INSTEAD OF s'executen en lloc de l'operació original. Són especialment útils per fer que les vistes complexes (joins de múltiples taules, vistes amb agregats) siguin actualitzables.

-- PostgreSQL suporta INSTEAD OF en vistes
CREATE VIEW v_productes_complets AS
SELECT p.id, p.nom, p.preu, c.nom AS categoria
FROM productes p
JOIN categories c ON c.id = p.categoria_id;

CREATE OR REPLACE FUNCTION trg_fn_update_vista_productes()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
    UPDATE productes
    SET nom = NEW.nom, preu = NEW.preu
    WHERE id = NEW.id;
    RETURN NEW;
END;
$$;

CREATE TRIGGER trg_upd_vista_productes
INSTEAD OF UPDATE ON v_productes_complets
FOR EACH ROW
EXECUTE FUNCTION trg_fn_update_vista_productes();
-- MySQL no suporta INSTEAD OF triggers directament.
-- Per fer vistes actualitzables, cal que la vista sigui 'simple'
-- (una sola taula, sense DISTINCT, GROUP BY, UNION, etc.)
-- o usar procediments emmagatzemats com a alternativa.

-- Alternativa: procediment que gestiona la lògica manualment
DELIMITER //
CREATE PROCEDURE actualitza_vista_producte(
    IN p_id INT,
    IN p_nom VARCHAR(200),
    IN p_preu DECIMAL(10,2)
)
BEGIN
    UPDATE productes SET nom = p_nom, preu = p_preu WHERE id = p_id;
END //
DELIMITER ;
-- SQL Server: INSTEAD OF molt usat per a vistes amb JOINs
CREATE VIEW v_productes_complets AS
SELECT p.id, p.nom, p.preu, c.nom AS categoria
FROM productes p
JOIN categories c ON c.id = p.categoria_id;
GO

CREATE OR ALTER TRIGGER trg_io_upd_productes
ON v_productes_complets
INSTEAD OF UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE p
    SET p.nom = i.nom, p.preu = i.preu
    FROM productes p
    JOIN inserted i ON i.id = p.id;
END;
GO
-- Oracle: INSTEAD OF disponible per a vistes no actualitzables
CREATE OR REPLACE VIEW v_productes_complets AS
SELECT p.id, p.nom, p.preu, c.nom AS categoria
FROM productes p
JOIN categories c ON c.id = p.categoria_id;
/

CREATE OR REPLACE TRIGGER trg_io_upd_productes
INSTEAD OF UPDATE ON v_productes_complets
FOR EACH ROW
BEGIN
    UPDATE productes
    SET nom = :NEW.nom, preu = :NEW.preu
    WHERE id = :OLD.id;
END trg_io_upd_productes;
/

Habilitar i deshabilitar triggers

-- Deshabilitar un trigger concret
ALTER TABLE productes DISABLE TRIGGER trg_audit_productes;

-- Deshabilitar tots els triggers d'una taula
ALTER TABLE productes DISABLE TRIGGER ALL;

-- Tornar a habilitar
ALTER TABLE productes ENABLE TRIGGER trg_audit_productes;
ALTER TABLE productes ENABLE TRIGGER ALL;
-- MySQL no té ALTER TRIGGER per habilitar/deshabilitar.
-- Cal eliminar-lo i tornar-lo a crear.
DROP TRIGGER IF EXISTS trg_audit_productes_update;

-- Alternativa: usar una variable de sessió com a flag
DELIMITER //
CREATE TRIGGER trg_audit_productes_update
AFTER UPDATE ON productes FOR EACH ROW
BEGIN
    IF @disable_audit IS NULL OR @disable_audit = 0 THEN
        INSERT INTO audit_productes (...) VALUES (...);
    END IF;
END //
DELIMITER ;

-- Per deshabilitar temporalment:
SET @disable_audit = 1;
-- ... operacions ...
SET @disable_audit = 0;
-- Deshabilitar un trigger
DISABLE TRIGGER trg_audit_productes ON productes;

-- Deshabilitar tots els triggers d'una taula
DISABLE TRIGGER ALL ON productes;

-- Tornar a habilitar
ENABLE TRIGGER trg_audit_productes ON productes;
ENABLE TRIGGER ALL ON productes;
-- Deshabilitar un trigger
ALTER TRIGGER trg_audit_productes DISABLE;

-- Deshabilitar tots els triggers d'una taula
ALTER TABLE productes DISABLE ALL TRIGGERS;

-- Tornar a habilitar
ALTER TRIGGER trg_audit_productes ENABLE;
ALTER TABLE productes ENABLE ALL TRIGGERS;

Advertències importants

Rendiment dels triggers

Els triggers s'executen sincrònament dins de la transacció de l'operació que els ha disparat. Qualsevol temps que tardi el trigger s'afegeix al temps de resposta de l'operació original:

  • Eviteu consultes costoses (sense índexs, fulls table scans) dins dels triggers.
  • En operacions massives (imports de milers de files), considereu deshabilitar temporalment els triggers no crítics.
  • Feu el trigger tan curt com sigui possible. Si la lògica és complexa, delegeu-la a un procediment emmagatzemat.
  • Monitoritzeu el temps d'execució dels triggers en entorns de producció.

Bucles de triggers (trigger cascades)

Un trigger que modifica una taula pot disparar altres triggers sobre aquella taula, que al seu torn en disparin d'altres... i crear un bucle infinit o una cascada de canvis inesperats.

Exemple perillós:

-- Trigger A: AFTER UPDATE ON taula_1 → UPDATE taula_2
-- Trigger B: AFTER UPDATE ON taula_2 → UPDATE taula_1  ← BUCLE!

Com evitar-ho:

  • Documenteu clarament quins triggers existeixen i quines taules toquen.
  • Useu flags de sessió o context per detectar si ja esteu dins d'un trigger.
  • PostgreSQL i SQL Server limiten la profunditat de la recursió de triggers.
  • A SQL Server, @@NESTLEVEL indica el nivell d'imbricació actual.
  • Preferiu dissenys on els triggers no modifiquen les taules que els disparen.

Inspecció de triggers existents

-- Llistar tots els triggers
SELECT
    trigger_name,
    event_manipulation,
    event_object_table,
    action_timing,
    action_orientation
FROM information_schema.triggers
WHERE trigger_schema = 'public'
ORDER BY event_object_table, trigger_name;
-- Llistar triggers de la BD actual
SHOW TRIGGERS;

-- Filtrar per taula
SHOW TRIGGERS WHERE `Table` = 'productes';

-- Veure el codi d'un trigger
SHOW CREATE TRIGGER trg_audit_productes_update;
-- Llistar triggers amb la taula associada
SELECT
    t.name AS trigger_nom,
    OBJECT_NAME(t.parent_id) AS taula,
    t.type_desc,
    t.is_disabled
FROM sys.triggers t
WHERE t.parent_class = 1;  -- 1 = object/column (triggers de taula)

-- Veure el codi font
SELECT OBJECT_DEFINITION(OBJECT_ID('trg_audit_productes'));
-- Llistar triggers de l'usuari actual
SELECT trigger_name, trigger_type, triggering_event,
       table_name, status
FROM user_triggers
ORDER BY table_name, trigger_name;

-- Veure el codi font
SELECT text
FROM user_source
WHERE name = 'TRG_AUDIT_PRODUCTES'
  AND type = 'TRIGGER'
ORDER BY line;

Miniactivitat — AC0405 · AC0406

Miniactivitat — AC0405 · AC0406 · Triggers de gestió d'inventari

Teniu les taules:

  • productes(id, nom, preu, estoc, estoc_minim)
  • comandes(id, client_id, data, estat, total)
  • linies_comanda(id, comanda_id, producte_id, quantitat, preu_unitari)
  • alertes_estoc(id, producte_id, estoc_actual, data_alerta, gestionada)

Tasca 1 — Trigger de validació d'estoc: Creeu un trigger BEFORE INSERT sobre linies_comanda que comprovi que hi ha prou estoc del producte sol·licitat. Si l'estoc és insuficient, ha de cancel·lar la inserció amb un missatge d'error clar que indiqui el producte i l'estoc disponible.

Tasca 2 — Trigger d'actualització d'estoc: Creeu un trigger AFTER INSERT sobre linies_comanda que descompti del camp estoc de productes la quantitat inserida.

Tasca 3 — Trigger d'alerta d'estoc mínim: Creeu un trigger AFTER UPDATE OF estoc sobre productes que detecti quan l'estoc cau per sota de estoc_minim i insereixi un registre a alertes_estoc.

Tasca 4 — Prova de cascada: Comproveu que els triggers de les tasques 1, 2 i 3 no provoquen cap conflicte entre ells quan s'insereix una línia de comanda que porta l'estoc per sota del mínim.

Entrega: Arxiu SQL amb els triggers, les taules de prova, les dades de prova (INSERT) i les consultes de verificació que demostrin que els tres triggers funcionen conjuntament de manera correcta.