CodeGym /Corsi /SQL SELF /Gestione degli errori e ritorno allo stato iniziale: EXCE...

Gestione degli errori e ritorno allo stato iniziale: EXCEPTION, RAISE

SQL SELF
Livello 53, Lezione 3
Disponibile

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

  1. Non usare ROLLBACK TO SAVEPOINT in PL/pgSQL! Ti darà errore di sintassi.
  2. Per rollback parziale usa sempre blocchi annidati BEGIN ... EXCEPTION ... END.
  3. Ricorda che COMMIT e ROLLBACK dentro le procedure riavviano la transazione — puoi usarli solo se la procedura gira in modalità autocommit!
  4. Logga gli errori in una tabella separata, così non perdi info sulle righe sbagliate.
  5. 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.

2
Compito
SQL SELF, livello 53, lezione 3
Bloccato
Uso di EXCEPTION e rollback
Uso di EXCEPTION e rollback
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION