JavaRush /Курсы /SQL SELF /Обработка ошибок и возврат в исходное состояние: EXCEPTIO...

Обработка ошибок и возврат в исходное состояние: EXCEPTION, RAISE

SQL SELF
53 уровень , 3 лекция
Открыта

Ошибки в PostgreSQL могут возникать по множеству причин: нарушение ограничений (NOT NULL, UNIQUE, CHECK), ошибки синтаксиса, дублирующиеся значения и т.д. Если не перехватывать и не обрабатывать такие ошибки, вся внешняя транзакция может быть полностью откатана. Для устойчивых бизнес-операций важно правильно их обрабатывать.

PL/pgSQL предоставляет мощный механизм блоков BEGIN ... EXCEPTION ... END для перехвата и обработки ошибок в функциях и процедурах. Они похожи на try-catch из Python или Java, но с особенностями работы в контексте транзакций PostgreSQL.

Важный момент:

Каждый блок BEGIN ... EXCEPTION ... END работает как “виртуальный savepoint”. Если возникает исключение — все изменения этого блока автоматически откатываются. Это единственный корректный способ частичного отката в функциях и процедурах на PL/pgSQL

Синтаксис обработки ошибок с помощью EXCEPTION

BEGIN
    -- основной код
EXCEPTION
    WHEN ТИП_ОШИБКИ THEN
        -- обработка конкретной ошибки
    WHEN ДРУГОЙ_ТИП_ОШИБКИ THEN
        -- другой обработчик
    WHEN OTHERS THEN
        -- обработка всех прочих ошибок
END;

Иллюстрация в функциях/процедурах

DO $$
BEGIN
    RAISE NOTICE 'Сейчас будет ошибка...';
    PERFORM 1 / 0;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE 'Деление на ноль перехвачено!';
END;
$$;

Пример: обновление с обработкой и откатом ошибок

Пусть есть таблица заказов:

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    amount NUMERIC NOT NULL,
    status TEXT NOT NULL
);

Создадим функцию, которая обновляет статус заказа и при ошибке ничего не меняет:

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;

        -- Симуляция ошибки
        IF new_status = 'FAIL' THEN
            RAISE EXCEPTION 'Симулируем ошибку!';
        END IF;

        RAISE NOTICE 'Статус заказа % обновлён', order_id;

    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'Ошибка при обновлении заказа %: %', order_id, SQLERRM;
            -- Все изменения внутри блока автоматически откатятся!
            -- Повторно выбрасываем ошибку для внешней обработки, если нужно
            RAISE;
    END;
END;
$$ LANGUAGE plpgsql;

Как это работает в процедурах с явным управлением транзакциями

В процедурах (CREATE PROCEDURE) можно использовать COMMIT, ROLLBACK, SAVEPOINT, но нельзя делать ROLLBACK TO SAVEPOINT. Если нужно откатить только часть операций внутри процедуры, используйте всё тот же BEGIN ... EXCEPTION ... END:

CREATE OR REPLACE PROCEDURE pay_order(order_id INT, amount NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
    -- Вся процедура может использовать COMMIT/ROLLBACK, но для отката отдельного этапа:
    BEGIN
        UPDATE accounts
        SET balance = balance - amount
        WHERE id = (SELECT account_id FROM orders WHERE id = order_id);

        -- ошибка
        IF amount < 0 THEN
            RAISE EXCEPTION 'Сумма не может быть отрицательной!';
        END IF;

        UPDATE orders SET status = 'PAID' WHERE id = order_id;

    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'Ошибка при обработке оплаты заказа %: %', order_id, SQLERRM;
            -- изменения в этом блоке откатятся автоматически
    END;

    COMMIT; -- Можно явно завершать транзакцию только в процедурах!
END;
$$;

Логирование ошибок

Важно не просто перехватывать ошибки, но и сохранять их для дальнейшего анализа.

CREATE TABLE error_log (
    id SERIAL PRIMARY KEY,
    order_id INT,
    error_message TEXT,
    error_time TIMESTAMP DEFAULT now()
);

В функции или процедуре:

EXCEPTION
    WHEN OTHERS THEN
        INSERT INTO error_log (order_id, error_message)
        VALUES (order_id, SQLERRM);
        RAISE NOTICE 'Ошибка занесена в журнал: %', SQLERRM;
        RAISE;

Важные ограничения и нюансы в PostgreSQL 17

Внутри функций (CREATE FUNCTION ... ) команды транзакционного управления (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT) использовать нельзя! Все функции выполняются целиком в рамках внешней транзакции.

Внутри процедур (CREATE PROCEDURE ... ) можно явно писать SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK. НО: ROLLBACK TO SAVEPOINT — ЗАПРЕЩЁН! (Вы получите ошибку синтаксиса, если попытаетесь использовать ROLLBACK TO SAVEPOINT в PL/pgSQL-процедуре).

Откат "части кода" внутри функций и процедур делается при помощи блоков BEGIN ... EXCEPTION ... END. При возникновении ошибки всё внутри блока откатывается автоматически, и исполнение может пойти дальше.

Процедуры (CREATE PROCEDURE) нельзя запускать внутри функции или через SELECT — только отдельной командой CALL ....

Как реально реализовать "частичный откат" в PL/pgSQL?

Единственный рабочий способ — использовать обработку ошибок через блоки BEGIN ... EXCEPTION ... END. Такой блок автоматически создаёт точку сохранения, и при ошибке откатывает изменения внутри блока, не затрагивая остальную часть процедуры/функции.

Пример с EXCEPTION (рекомендуемый подход):

CREATE OR REPLACE PROCEDURE demo_savepoint()
LANGUAGE plpgsql
AS $$
BEGIN
    -- Какой-то код
    BEGIN
        -- Здесь ошибка не приведёт к откату всей процедуры,
        -- только этого блока!
        INSERT INTO demo VALUES ('bad data'); -- возможно, вызовет ошибку
    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'Ошибка обработана, изменения внутри блока отменены';
    END;
    -- Здесь выполнение продолжается!
END;
$$;

Пример: загрузка пакета данных с защитой от отката всего процесса

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 ('Ошибка в записи: ' || rec.col1 || ': ' || SQLERRM);
            -- изменения внутри этого блока отменены!
        END;
    END LOOP;
    COMMIT;  -- допустимо только если процедура запущена вне явной внешней транзакции!
END;
$$;

-- Вызов процедуры
CALL load_big_batch();

Обратите внимание: если вызвать такую процедуру из клиента, у которого уже начата транзакция (например, Python с autocommit=False), выполнение COMMIT или SAVEPOINT внутри процедуры вызовет ошибку.

Советы по работе с вложенными точками сохранения и EXCEPTION

  1. Не используйте ROLLBACK TO SAVEPOINT в PL/pgSQL! Это вызовет синтаксическую ошибку.
  2. Для частичного отката всегда используйте вложенные блоки BEGIN ... EXCEPTION ... END.
  3. Не забывайте, что COMMIT и ROLLBACK внутри процедур перезапускают транзакцию — их можно вызывать только когда процедура запущена в autocommit-режиме!
  4. Логируйте ошибки в отдельную таблицу, чтобы не терять информацию о некорректных строках.
  5. Если бизнес-операция должна быть строго атомарной (всё-или-ничего) — делайте её функцией, без COMMIT/ROLLBACK внутри; если нужна обработка поэтапно — реализуйте как процедуру.

Пример: импорт по пакетам с частичной обработкой

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 ('Дубликат: ' || rec.data);
            WHEN OTHERS THEN
                INSERT INTO import_log (msg)
                VALUES ('Ошибка: ' || rec.data || ' — ' || SQLERRM);
        END;
    END LOOP;
END;
$$;

Главное, что нужно запомнить для PostgreSQL 17:

В PL/pgSQL процедурах разрешены SAVEPOINT, RELEASE SAVEPOINT, COMMIT, ROLLBACK, но ROLLBACK TO SAVEPOINT — нельзя.

"Частичный откат" внутри функций и процедур реализуется только с помощью вложенных блоков BEGIN ... EXCEPTION ... END.

Транзакциями лучше управлять снаружи (через параметры подключения и autocommit), а внутри процедур — использовать описанные механизмы обработки ошибок.

2
Задача
SQL SELF, 53 уровень, 3 лекция
Недоступна
Использование EXCEPTION и возврат
Использование EXCEPTION и возврат
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ra Уровень 35 Student
21 августа 2025
Как ни странно, код задачи выполнился с 1 раза 😂 Обычно какой-нибудь да косяк, благодаря мудрёному синтаксису.