Gli errori in PostgreSQL possono succedere per mille motivi: violazione di constraint (NOT NULL, UNIQUE, CHECK), errori di sintassi, valori duplicati ecc. Se non li intercetti e non li gestisci, tutta la transazione esterna può essere rollbackata completamente. Per operazioni business affidabili è fondamentale gestirli bene.
PL/pgSQL ti dà un meccanismo potente con i blocchi BEGIN ... EXCEPTION ... END per intercettare e gestire errori in funzioni e procedure. Sono simili ai try-catch di Python o Java, ma con alcune particolarità nel contesto delle transazioni PostgreSQL.
Punto importante:
Ogni blocco BEGIN ... EXCEPTION ... END funziona come un “savepoint virtuale”. Se scatta un’eccezione — tutte le modifiche di quel blocco vengono rollbackate in automatico. Questo è l’unico modo corretto di fare rollback parziale in funzioni e procedure PL/pgSQL.
Sintassi della gestione errori con EXCEPTION
BEGIN
-- codice principale
EXCEPTION
WHEN TIPO_ERRORE THEN
-- gestione di uno specifico errore
WHEN ALTRO_TIPO_ERRORE THEN
-- altro handler
WHEN OTHERS THEN
-- gestione di tutti gli altri errori
END;
Illustrazione in funzioni/procedure
DO $$
BEGIN
RAISE NOTICE 'Sta per arrivare un errore...';
PERFORM 1 / 0;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Divisione per zero intercettata!';
END;
$$;
Esempio: update con gestione e rollback degli errori
Supponiamo di avere una tabella ordini:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
amount NUMERIC NOT NULL,
status TEXT NOT NULL
);
Creiamo una funzione che aggiorna lo status di un ordine e, in caso di errore, non cambia nulla:
CREATE OR REPLACE FUNCTION update_order_status(order_id INT, new_status TEXT)
RETURNS VOID AS $$
BEGIN
BEGIN
UPDATE orders
SET status = new_status
WHERE id = order_id;
-- Simulazione di errore
IF new_status = 'FAIL' THEN
RAISE EXCEPTION 'Simuliamo un errore!';
END IF;
RAISE NOTICE 'Status dell\'ordine % aggiornato', order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Errore durante l\'aggiornamento dell\'ordine %: %', order_id, SQLERRM;
-- Tutte le modifiche dentro il blocco vengono rollbackate in automatico!
-- Rilanciamo l'errore per gestione esterna, se serve
RAISE;
END;
END;
$$ LANGUAGE plpgsql;
Come funziona nelle procedure con gestione esplicita delle transazioni
Nelle procedure (CREATE PROCEDURE) puoi usare COMMIT, ROLLBACK, SAVEPOINT, ma non puoi fare ROLLBACK TO SAVEPOINT. Se vuoi rollbackare solo una parte delle operazioni dentro la procedura, usa sempre BEGIN ... EXCEPTION ... END:
CREATE OR REPLACE PROCEDURE pay_order(order_id INT, amount NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
-- Tutta la procedura può usare COMMIT/ROLLBACK, ma per rollbackare solo una fase:
BEGIN
UPDATE accounts
SET balance = balance - amount
WHERE id = (SELECT account_id FROM orders WHERE id = order_id);
-- errore
IF amount < 0 THEN
RAISE EXCEPTION 'L\'importo non può essere negativo!';
END IF;
UPDATE orders SET status = 'PAID' WHERE id = order_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Errore durante il pagamento dell\'ordine %: %', order_id, SQLERRM;
-- le modifiche in questo blocco vengono rollbackate in automatico
END;
COMMIT; -- Puoi chiudere la transazione esplicitamente solo nelle procedure!
END;
$$;
Log degli errori
Non basta solo intercettare gli errori, è importante anche salvarli per analisi future.
CREATE TABLE error_log (
id SERIAL PRIMARY KEY,
order_id INT,
error_message TEXT,
error_time TIMESTAMP DEFAULT now()
);
Nella funzione o procedura:
EXCEPTION
WHEN OTHERS THEN
INSERT INTO error_log (order_id, error_message)
VALUES (order_id, SQLERRM);
RAISE NOTICE 'Errore inserito nel log: %', SQLERRM;
RAISE;
Limiti e particolarità importanti in PostgreSQL 17
Dentro le funzioni (CREATE FUNCTION ... ) non puoi usare comandi di gestione transazionale (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT)! Tutte le funzioni vengono eseguite interamente nella transazione esterna.
Dentro le procedure (CREATE PROCEDURE ... ) puoi scrivere esplicitamente SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK. MA: ROLLBACK TO SAVEPOINT — VIETATO! (Riceverai un errore di sintassi se provi a usare ROLLBACK TO SAVEPOINT in una procedura PL/pgSQL).
Il rollback di "parte del codice" dentro funzioni e procedure si fa con i blocchi BEGIN ... EXCEPTION ... END. Se c’è un errore, tutto dentro il blocco viene rollbackato in automatico e l’esecuzione può continuare.
Le procedure (CREATE PROCEDURE) non si possono lanciare dentro una funzione o tramite SELECT — solo con il comando CALL ... separato.
Come si fa davvero il "rollback parziale" in PL/pgSQL?
L’unico modo che funziona — usare la gestione errori con i blocchi BEGIN ... EXCEPTION ... END. Questo blocco crea in automatico un savepoint e, in caso di errore, rollbacka solo le modifiche interne, senza toccare il resto della procedura/funzione.
Esempio con EXCEPTION (approccio consigliato):
CREATE OR REPLACE PROCEDURE demo_savepoint()
LANGUAGE plpgsql
AS $$
BEGIN
-- Qualche codice
BEGIN
-- Qui un errore non fa rollbackare tutta la procedura,
-- solo questo blocco!
INSERT INTO demo VALUES ('dati sbagliati'); -- potrebbe causare errore
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Errore gestito, modifiche dentro il blocco annullate';
END;
-- Qui l’esecuzione continua!
END;
$$;
Esempio: caricamento di un batch di dati con protezione dal rollback totale
CREATE OR REPLACE PROCEDURE load_big_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM import_table LOOP
BEGIN
INSERT INTO target_table (col1, col2)
VALUES (rec.col1, rec.col2);
EXCEPTION WHEN OTHERS THEN
INSERT INTO import_errors (err_msg)
VALUES ('Errore nel record: ' || rec.col1 || ': ' || SQLERRM);
-- modifiche dentro questo blocco annullate!
END;
END LOOP;
COMMIT; -- permesso solo se la procedura è lanciata fuori da una transazione esterna!
END;
$$;
-- Chiamata della procedura
CALL load_big_batch();
Attenzione: se chiami questa procedura da un client che ha già una transazione aperta (tipo Python con autocommit=False), fare COMMIT o SAVEPOINT dentro la procedura causerà errore.
Consigli per lavorare con savepoint annidati ed EXCEPTION
- Non usare ROLLBACK TO SAVEPOINT in PL/pgSQL! Ti darà errore di sintassi.
- Per rollback parziale usa sempre blocchi annidati BEGIN ... EXCEPTION ... END.
- Ricorda che COMMIT e ROLLBACK dentro le procedure riavviano la transazione — puoi usarli solo se la procedura gira in modalità autocommit!
- Logga gli errori in una tabella separata, così non perdi info sulle righe sbagliate.
- Se l’operazione business deve essere atomica (tutto-o-niente) — falla come funzione, senza COMMIT/ROLLBACK dentro; se serve gestire a step — implementala come procedura.
Esempio: import a batch con gestione parziale
CREATE OR REPLACE PROCEDURE import_batch()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM staging_table LOOP
BEGIN
INSERT INTO data_table (data)
VALUES (rec.data);
EXCEPTION
WHEN unique_violation THEN
INSERT INTO import_log (msg)
VALUES ('Duplicato: ' || rec.data);
WHEN OTHERS THEN
INSERT INTO import_log (msg)
VALUES ('Errore: ' || rec.data || ' — ' || SQLERRM);
END;
END LOOP;
END;
$$;
Cosa ricordare per PostgreSQL 17:
In PL/pgSQL procedure sono permessi SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK, ma ROLLBACK TO SAVEPOINT — no.
Il "rollback parziale" dentro funzioni e procedure si fa solo con blocchi annidati BEGIN ... EXCEPTION ... END.
Meglio gestire le transazioni dall’esterno (tramite parametri di connessione e autocommit), e dentro le procedure — usare i meccanismi di gestione errori descritti.
GO TO FULL VERSION