Quando una modifica a una tabella deve aggiornare un registro, bloccare un dato incoerente o sincronizzare automaticamente un’altra informazione, un trigger SQL può svolgere il lavoro direttamente nel database. In questa guida mostro come funziona, quali eventi può intercettare, come si scrive nei principali sistemi relazionali e quali rischi valutare prima di usarlo in produzione.
Il meccanismo giusto per automatizzare reazioni dentro il database
- Un trigger esegue automaticamente codice dopo o prima di un evento come INSERT, UPDATE o DELETE.
- BEFORE, AFTER e INSTEAD OF determinano il momento e il modo in cui viene eseguita l’azione.
- Audit, controlli e sincronizzazioni sono gli usi più pratici, ma non ogni logica applicativa dovrebbe finire nel database.
- Le differenze tra MySQL, PostgreSQL e SQL Server riguardano sintassi, livello di esecuzione e gestione delle righe modificate.
- Test, logging e documentazione sono indispensabili perché un trigger lavora spesso in modo invisibile al codice dell’applicazione.

Che cos’è un trigger SQL e quando si attiva
Un trigger SQL è un oggetto associato a una tabella, a una vista o, in alcuni database, a eventi dello schema. Quando si verifica l’evento previsto, il database esegue automaticamente una funzione o una serie di istruzioni. Il vantaggio principale è che la regola viene applicata indipendentemente dall’applicazione che modifica i dati.
Gli eventi più comuni sono INSERT, per l’inserimento di una riga, UPDATE, per la modifica, e DELETE, per la cancellazione. In SQL Server esistono anche trigger DDL, legati a operazioni come CREATE, ALTER e DROP, oltre ai trigger di accesso alla sessione.
Per esempio, se un ordine passa allo stato “pagato”, il trigger può registrare l’evento in una tabella storica. Se qualcuno prova a inserire una quantità negativa, può bloccare l’operazione. Io considero questa tecnica particolarmente utile quando la regola deve valere per tutti i client, non solo per il programma principale.
Le azioni più adatte
- salvare una cronologia delle modifiche;
- aggiornare contatori o valori derivati;
- impedire dati palesemente incoerenti;
- mantenere una tabella di audit;
- sincronizzare dati strettamente collegati;
- applicare regole tecniche che non devono essere aggirate.
Non userei invece un trigger per inviare email, chiamare API esterne o orchestrare processi complessi. In questi casi è spesso più sicuro registrare l’evento in una coda e lasciare il lavoro a un servizio applicativo.
BEFORE, AFTER e INSTEAD OF cambiano il risultato
La scelta del momento di esecuzione non è un dettaglio sintattico. Determina quali dati sono disponibili, se è ancora possibile modificarli e come reagisce il database in caso di errore.
| Tipo | Quando opera | Uso tipico |
|---|---|---|
| BEFORE | Prima del completamento dell’operazione | Validare o normalizzare valori |
| AFTER | Dopo l’operazione sulla riga | Scrivere audit o aggiornare dati collegati |
| INSTEAD OF | Al posto dell’operazione richiesta | Gestire modifiche su viste complesse |
Un trigger BEFORE è adatto, per esempio, a trasformare un codice in maiuscolo oppure a rifiutare una data non valida. Un trigger AFTER è più naturale quando la modifica deve prima andare a buon fine, come nel caso della registrazione di una variazione in una tabella di storico.
Bisogna però distinguere tra esecuzione per riga ed esecuzione per istruzione. In MySQL i trigger DML vengono eseguiti per ogni riga coinvolta. PostgreSQL supporta trigger per riga e per istruzione, mentre SQL Server gestisce normalmente il trigger in rapporto all’intera istruzione e rende disponibili le tabelle virtuali inserted e deleted.
Questa differenza può cambiare molto le prestazioni. Un UPDATE che modifica 100.000 righe può attivare 100.000 esecuzioni in un sistema row-level. Prima di adottare un trigger, verifico sempre il volume massimo previsto e testo anche le operazioni effettuate in blocco.
Un esempio pratico con MySQL
Supponiamo di avere una tabella ordini e una tabella che conserva la cronologia degli stati. Il trigger seguente registra ogni nuovo ordine subito dopo l’inserimento.
CREATE TABLE ordine_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
ordine_id BIGINT NOT NULL,
stato VARCHAR(30) NOT NULL,
creato_il TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
DELIMITER //
CREATE TRIGGER ordine_dopo_inserimento
AFTER INSERT ON ordini
FOR EACH ROW
BEGIN
INSERT INTO ordine_log (ordine_id, stato)
VALUES (NEW.id, NEW.stato);
END//
DELIMITER ;La parola NEW rappresenta i valori della nuova riga. Nei trigger associati a UPDATE si possono normalmente confrontare i valori precedenti e quelli nuovi, mentre in un DELETE interessa soprattutto il valore OLD.
Un secondo caso utile è la validazione di un importo. In un trigger BEFORE si può controllare che il valore sia positivo e interrompere l’operazione con un errore. Questa protezione è efficace solo se la regola appartiene davvero al dominio dei dati e non dipende da informazioni esterne al database.
DELIMITER //
CREATE TRIGGER ordine_prima_inserimento
BEFORE INSERT ON ordini
FOR EACH ROW
BEGIN
IF NEW.totale < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Il totale non può essere negativo';
END IF;
END//
DELIMITER ;In MySQL un errore nel trigger può far fallire l’intera istruzione. Con tabelle transazionali, come quelle InnoDB, la modifica viene normalmente annullata insieme alle altre operazioni della stessa transazione. È un comportamento utile, ma va verificato nell’ambiente concreto prima di farne affidamento.
Come cambia l’approccio con PostgreSQL e SQL Server
La logica generale resta la stessa, ma la sintassi non è intercambiabile. PostgreSQL richiede in genere una trigger function, cioè una funzione con tipo di ritorno trigger, che viene poi collegata all’evento.
CREATE OR REPLACE FUNCTION registra_nuovo_ordine()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO ordine_log (ordine_id, stato)
VALUES (NEW.id, NEW.stato);
RETURN NEW;
END;
$$;
CREATE TRIGGER ordine_dopo_inserimento
AFTER INSERT ON ordini
FOR EACH ROW
EXECUTE FUNCTION registra_nuovo_ordine();Il valore restituito è importante. In un trigger BEFORE PostgreSQL può restituire NEW per accettare la riga, modificarla prima del salvataggio oppure restituire NULL per ignorare quella specifica operazione, secondo il caso d’uso.
SQL Server segue un modello diverso. Il trigger può lavorare su più righe contemporaneamente e deve quindi evitare l’errore comune di trattare la tabella inserted come se contenesse una sola riga.
CREATE TRIGGER trg_log_ordine
ON dbo.Ordini
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.OrdineLog (OrdineId, Stato)
SELECT id, stato
FROM inserted;
END;Questo approccio basato su SELECT è fondamentale quando una sola istruzione INSERT inserisce molte righe. Un trigger scritto con variabili scalari o con una logica pensata per una singola riga può produrre risultati incompleti senza generare errori evidenti.
Vantaggi reali e limiti da considerare
Il punto di forza più evidente è la centralizzazione delle regole. Un’importazione da script, un pannello amministrativo e un servizio API passano tutti dallo stesso controllo. Per l’audit dei dati questo è spesso più affidabile di una registrazione affidata soltanto al codice dell’interfaccia.
Il rovescio della medaglia è la scarsa visibilità. Chi legge la query principale potrebbe non sapere che, dietro un semplice INSERT, partono altre scritture, validazioni o aggiornamenti. In sistemi grandi questa logica nascosta rende più difficile il debugging e può rallentare operazioni che sembravano banali.
| Situazione | Trigger consigliato | Alternativa da valutare |
|---|---|---|
| Storico delle modifiche | Sì, se l’audit deve essere completo | Audit nativo o CDC |
| Calcolo di un campo derivato | Solo per regole semplici | Calcolo nell’applicazione o vista |
| Invio di notifiche | No per chiamate dirette | Coda di eventi o job asincrono |
| Validazione strutturale | Sì, se vale per ogni client | Vincoli CHECK, UNIQUE o FOREIGN KEY |
| Processi aziendali complessi | Di solito no | Servizio applicativo orchestrato |
Prima di creare un trigger per controllare una condizione semplice, verifico se esiste un vincolo nativo. Una chiave esterna, un vincolo UNIQUE o un CHECK sono spesso più chiari, più facili da analizzare e meglio ottimizzati dal database.
Gli errori più comuni nella progettazione
Il primo errore è inserire troppa logica nello stesso trigger. Un oggetto che valida dati, aggiorna cinque tabelle, registra log e tenta anche di inviare notifiche diventa rapidamente difficile da mantenere. Preferisco trigger piccoli, con un solo scopo e nomi che descrivono chiaramente evento e tabella.
Il secondo problema è dimenticare le operazioni massive. Una procedura che funziona con un singolo INSERT può diventare molto lenta quando l’applicazione importa migliaia di righe. In questi casi misuro il tempo con e senza trigger e controllo gli indici delle tabelle toccate indirettamente.
Un’altra fonte di guai sono le ricorsioni e le dipendenze circolari. Il trigger sulla tabella A aggiorna B, quello su B aggiorna C e un terzo torna su A. Il risultato può essere un errore, una catena di esecuzioni inattesa o un consumo eccessivo di risorse.
Leggi anche: if __name__ == "__main__" in Python - guida pratica
Una verifica pratica prima del rilascio
- Descrivere in una frase l’unico obiettivo del trigger.
- Testare INSERT, UPDATE e DELETE singoli e massivi.
- Verificare cosa accade quando la logica genera un errore.
- Controllare transazioni, rollback e livelli di isolamento.
- Misurare l’impatto sui tempi e sui lock.
- Documentare dipendenze, permessi e procedura di disattivazione.
Io aggiungo sempre test che modificano più righe, perché sono quelli che fanno emergere più spesso i difetti. Un database può sembrare veloce in sviluppo e diventare un collo di bottiglia quando il trigger si attiva migliaia di volte durante un’importazione.
Quando il trigger è davvero la scelta migliore
Userei questa tecnica quando la regola deve essere applicata nel punto più vicino possibile ai dati, soprattutto per audit, vincoli applicativi essenziali e aggiornamenti brevi tra tabelle strettamente correlate.
La eviterei quando il processo richiede chiamate di rete, ritardi, tentativi automatici, integrazioni con servizi cloud o una gestione articolata degli errori. In questi casi il trigger può limitarsi a registrare un evento, mentre un worker o una coda si occupano del resto.
La scelta migliore non dipende dal fatto che un trigger sia potente, ma dal fatto che sia prevedibile. Se il comportamento può essere spiegato facilmente, testato in transazione e monitorato nel tempo, il database diventa un buon luogo per automatizzarlo. Se invece la logica è invisibile, lunga e piena di eccezioni, conviene lasciarla a un livello applicativo più semplice da osservare.