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. ВАЖНО: это не бесплатно — каждый BEGIN с обработчиком EXCEPTION создаёт subtransaction, что заметно дороже обычного блока без EXCEPTION. Не оборачивайте каждый INSERT в EXCEPTION «на всякий случай» — используйте только там, где действительно нужен частичный откат.

Синтаксис обработки ошибок с помощью 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, RELEASE SAVEPOINT и ROLLBACK TO SAVEPOINT — все три команды в PL/pgSQL запрещены. Если нужно откатить только часть операций внутри процедуры, используйте всё тот же 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 ... ) можно явно писать COMMIT и ROLLBACK. НО: SAVEPOINT, RELEASE 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. Не используйте SAVEPOINT, RELEASE SAVEPOINT и 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 процедурах разрешены COMMIT и ROLLBACK, а SAVEPOINT, RELEASE SAVEPOINT и ROLLBACK TO SAVEPOINT — нельзя (все три запрещены полностью).

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

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

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