RAISE в Oracle SQL. Введение
RAISE — оператор PL/SQL для возбуждения исключений. Им поднимают как предопределённые, так и пользовательские ошибки, а также «пере‑бросают» текущую ошибку из обработчика. В паре с блоком EXCEPTION, конструкциями WHEN ... THEN и функцией RAISE_APPLICATION_ERROR формирует предсказуемую обработку и протоколирование ошибок.
Где используют
- Валидация бизнес‑правил: проверки в процедурах, функциях и триггерах.
- Контроль транзакций: откат при нарушении условий, сигнал наверх вызывающему коду.
- Диагностика: логирование
SQLCODE/SQLERRMи последующий проброс для сохранения трассировки. - API‑контракты: точные сообщения через
RAISE_APPLICATION_ERRORс кодами -20000..-20999.
Синтаксис
-- Пере‑брос текущего исключения в обработчике
BEGIN
-- ...
EXCEPTION
WHEN OTHERS THEN
RAISE; -- без имени — повторно возбуждает текущую ошибку
END;
/
-- Возбуждение именованного исключения
DECLARE
e_bad_state EXCEPTION;
BEGIN
IF some_condition THEN
RAISE e_bad_state;
END IF;
END;
/
-- Пользовательское сообщение/код
BEGIN
IF invalid_input THEN
RAISE_APPLICATION_ERROR(-20010, 'Bad input');
END IF;
END;
/RAISE;— повторно возбуждает текущую ошибку внутри блокаEXCEPTION.RAISE имя;— возбуждение объявленного исключения или предопределённого.RAISE_APPLICATION_ERROR(-20000..-20999, message [, keep_errors])— ошибка с сообщением пользователя.
100 примеров
1. Пере‑брос исключения в обработчике (сохранить стек)
BEGIN
UPDATE accounts SET balance = balance - :amt WHERE id = :src;
UPDATE accounts SET balance = balance + :amt WHERE id = :dst;
EXCEPTION
WHEN OTHERS THEN
-- логируем и поднимаем дальше
INSERT INTO err_log(ts, code, msg) VALUES (SYSTIMESTAMP, SQLCODE, SQLERRM);
RAISE;
END;
/2. Возбуждение именованного исключения по бизнес‑правилу
DECLARE
e_limit EXCEPTION;
BEGIN
IF :amount > 10000 THEN
RAISE e_limit;
END IF;
END;
/3. Пользовательская ошибка с понятным сообщением
BEGIN
IF :qty <= 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Quantity must be positive');
END IF;
END;
/4. Привязка номера ошибки с PRAGMA EXCEPTION_INIT
DECLARE
e_deadlock EXCEPTION;
PRAGMA EXCEPTION_INIT(e_deadlock, -60); -- ORA-00060 deadlock
BEGIN
NULL;
EXCEPTION
WHEN e_deadlock THEN
-- обработка и повтор
RAISE;
END;
/5. Ре‑рейз конкретного именованного исключения
DECLARE
e_wrong_state EXCEPTION;
BEGIN
BEGIN
-- внутренняя проверка
IF :state NOT IN ('NEW','OPEN') THEN
RAISE e_wrong_state;
END IF;
EXCEPTION
WHEN e_wrong_state THEN
-- записали подробности и пере‑бросили наружу
INSERT INTO audit_log(details) VALUES ('wrong state: ' || :state);
RAISE;
END;
END;
/6. В триггере при нарушении инварианта
CREATE OR REPLACE TRIGGER trg_orders_chk
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
BEGIN
IF :NEW.total_amount < 0 THEN
RAISE_APPLICATION_ERROR(-20020, 'total_amount cannot be negative');
END IF;
END;
/7. В функции при неверном аргументе
CREATE OR REPLACE FUNCTION get_discount(p_customer_id NUMBER)
RETURN NUMBER IS
BEGIN
IF p_customer_id IS NULL THEN
RAISE_APPLICATION_ERROR(-20005, 'customer_id is required');
END IF;
RETURN 0.05;
END;
/8. Логирование в автономной транзакции + проброс
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO err_log(ts, code, msg) VALUES (SYSTIMESTAMP, SQLCODE, SQLERRM);
COMMIT; -- обязательно в автономной
END;
/
BEGIN
-- основной блок
NULL;
EXCEPTION
WHEN OTHERS THEN
-- вызываем автономное логирование и пере‑брос
NULL; -- здесь мог бы быть вызов логгера
RAISE;
END;
/9. WHEN OTHERS без проглатывания ошибок
BEGIN
-- risky
NULL;
EXCEPTION
WHEN OTHERS THEN
-- можно добавить лог
RAISE;
END;
/10. Сохранение SQLERRM в переменную и проброс
DECLARE
v_msg VARCHAR2(4000);
BEGIN
-- ...
EXCEPTION
WHEN OTHERS THEN
v_msg := SQLERRM;
INSERT INTO err_log(msg) VALUES (v_msg);
RAISE;
END;
/11. SAVEPOINT и откат перед пользовательской ошибкой
BEGIN
SAVEPOINT before_update;
UPDATE t SET val = val + 1 WHERE id = :id;
IF :fail = 1 THEN
ROLLBACK TO before_update;
RAISE_APPLICATION_ERROR(-20030, 'Manual rollback');
END IF;
COMMIT;
END;
/12. Композитная проверка в PROCEDURE
CREATE OR REPLACE PROCEDURE post_payment(p_order_id NUMBER, p_amt NUMBER) IS
BEGIN
IF p_amt <= 0 THEN
RAISE_APPLICATION_ERROR(-20011, 'Amount must be > 0');
END IF;
-- ...
EXCEPTION
WHEN OTHERS THEN
-- аудит и проброс
INSERT INTO err_log(code, msg) VALUES (SQLCODE, SQLERRM);
RAISE;
END;
/13. В CURSOR‑цикле при нарушении ожиданий
DECLARE
CURSOR c IS SELECT id, qty FROM items WHERE status = 'NEW';
BEGIN
FOR r IN c LOOP
IF r.qty < 0 THEN
RAISE_APPLICATION_ERROR(-20012, 'Negative qty for id=' || r.id);
END IF;
END LOOP;
END;
/14. FORALL и возврат наружу по первому сбою
DECLARE
TYPE t_ids IS TABLE OF NUMBER;
v_ids t_ids := t_ids(1,2,3);
BEGIN
FORALL i IN 1..v_ids.COUNT SAVE EXCEPTIONS
UPDATE items SET processed = 1 WHERE id = v_ids(i);
EXCEPTION
WHEN OTHERS THEN
-- можно разобрать SQL%BULK_EXCEPTIONS, затем проброс
RAISE;
END;
/15. TRIGGER блокирует изменение защищённого поля
CREATE OR REPLACE TRIGGER trg_users_ro
BEFORE UPDATE OF created_at ON users
FOR EACH ROW
BEGIN
RAISE_APPLICATION_ERROR(-20040, 'created_at is read-only');
END;
/16. PRAGMA EXCEPTION_INIT для внешнего ключа
DECLARE
e_fk EXCEPTION;
PRAGMA EXCEPTION_INIT(e_fk, -2292); -- ORA-02292: child record found
BEGIN
DELETE FROM parent WHERE id = :id;
EXCEPTION
WHEN e_fk THEN
RAISE_APPLICATION_ERROR(-20050, 'Cannot delete: children exist');
END;
/17. Единая политика ошибок в PACKAGE
CREATE OR REPLACE PACKAGE api AS
PROCEDURE ensure_positive(p_val NUMBER);
END api;
/
CREATE OR REPLACE PACKAGE BODY api AS
PROCEDURE ensure_positive(p_val NUMBER) IS
BEGIN
IF p_val <= 0 THEN
RAISE_APPLICATION_ERROR(-20060, 'Value must be positive');
END IF;
END;
END api;
/18. Рекурсивный алгоритм: защита от переполнения глубины
DECLARE
e_depth EXCEPTION;
v_depth NUMBER := 0;
PROCEDURE walk(p_id NUMBER) IS
BEGIN
v_depth := v_depth + 1;
IF v_depth > 1000 THEN
RAISE e_depth;
END IF;
-- recurse ...
END;
BEGIN
walk(1);
END;
/19. Повторная попытка после DEADLOCK, затем проброс
DECLARE
e_deadlock EXCEPTION;
PRAGMA EXCEPTION_INIT(e_deadlock, -60);
v_tries PLS_INTEGER := 0;
BEGIN
<<retry>>
BEGIN
v_tries := v_tries + 1;
UPDATE t SET val = val + 1 WHERE id = 1;
EXCEPTION
WHEN e_deadlock THEN
IF v_tries < 3 THEN
DBMS_LOCK.SLEEP(0.2);
GOTO retry;
ELSE
RAISE; -- отдать наружу
END IF;
END;
END;
/20. Информативный бизнес‑код вместо системного
BEGIN
IF NOT EXISTS (SELECT 1 FROM users WHERE id = :id) THEN
RAISE_APPLICATION_ERROR(-20070, 'User not found: ' || :id);
END IF;
END;
/Еще 20 примеров.
21. Конвертация ORA‑00001 в доменную ошибку
DECLARE
e_dup EXCEPTION;
PRAGMA EXCEPTION_INIT(e_dup, -1); -- ORA-00001 unique constraint
BEGIN
INSERT INTO accounts(id) VALUES (:id);
EXCEPTION
WHEN e_dup THEN
RAISE_APPLICATION_ERROR(-20080, 'Account already exists');
END;
/22. WHEN OTHERS: лог параметров и проброс
BEGIN
-- работа с параметрами :a, :b
EXCEPTION
WHEN OTHERS THEN
INSERT INTO err_log(details)
VALUES ('a='||:a||', b='||:b||', err='||SQLERRM);
RAISE;
END;
/23. VALIDATE‑шаг бизнес‑процедуры
CREATE OR REPLACE PROCEDURE validate_order(p_id NUMBER) IS
v_ok BOOLEAN := TRUE;
BEGIN
IF v_ok = FALSE THEN
RAISE_APPLICATION_ERROR(-20090, 'Validation failed for order '||p_id);
END IF;
END;
/24. Пользовательский тип исключений и проверка баланса
DECLARE
e_low_balance EXCEPTION;
PRAGMA EXCEPTION_INIT(e_low_balance, -20123);
BEGIN
IF :balance < :needed THEN
RAISE e_low_balance;
END IF;
END;
/25. Парсер входных данных: обязательный разделитель
CREATE OR REPLACE PROCEDURE import_row(p_line VARCHAR2) IS
BEGIN
IF INSTR(p_line,';') = 0 THEN
RAISE_APPLICATION_ERROR(-20101, 'Delimiter ; not found');
END IF;
END;
/26. Тайм‑аут длительной операции
DECLARE
v_start TIMESTAMP := SYSTIMESTAMP;
BEGIN
-- цикл
IF SYSTIMESTAMP - v_start > INTERVAL '5' SECOND THEN
RAISE_APPLICATION_ERROR(-20105, 'Timeout');
END IF;
END;
/27. PIPELINED‑функция: защита аргумента
CREATE OR REPLACE FUNCTION f(p IN NUMBER)
RETURN SYS.ODCINUMBERLIST PIPELINED IS
BEGIN
IF p < 0 THEN
RAISE_APPLICATION_ERROR(-20110, 'p must be >= 0');
END IF;
PIPE ROW(p);
RETURN;
END;
/28. Частичный откат и информирование о причине
BEGIN
SAVEPOINT sp1;
UPDATE t SET val = 1 WHERE id = 1;
-- ошибка
ROLLBACK TO sp1;
RAISE_APPLICATION_ERROR(-20120, 'Partial rollback done');
END;
/29. TRIGGER вместо молчаливого игнора удаления
CREATE OR REPLACE TRIGGER trg_no_delete_users
BEFORE DELETE ON users
BEGIN
RAISE_APPLICATION_ERROR(-20130, 'Deleting users is forbidden');
END;
/30. Пакетное задание: проверка конфигурации
BEGIN
IF :cfg IS NULL THEN
RAISE_APPLICATION_ERROR(-20140, 'Job misconfigured');
END IF;
END;
/31. Остановка в DEV‑контуре (по контексту)
BEGIN
IF SYS_CONTEXT('USERENV','INSTANCE_NAME') LIKE '%-DEV' THEN
RAISE_APPLICATION_ERROR(-20150, 'Blocked in DEV');
END IF;
END;
/32. Граф: обнаружен цикл
DECLARE
e_cycle EXCEPTION;
BEGIN
-- обнаружен цикл
RAISE e_cycle;
END;
/33. Короткий выход из вложенных блоков
BEGIN
BEGIN
IF :flag = 1 THEN
RAISE_APPLICATION_ERROR(-20160, 'Early exit');
END IF;
END;
END;
/34. Проверка прав доступа
BEGIN
IF NOT has_priv(:user_id, 'WRITE') THEN
RAISE_APPLICATION_ERROR(-20170, 'Access denied');
END IF;
END;
/35. NO_DATA_FOUND → бизнес‑ошибка
BEGIN
SELECT name INTO :v FROM t WHERE id = :id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20180, 'Record not found');
END;
/36. TOO_MANY_ROWS → доменная ошибка
BEGIN
SELECT id INTO :v FROM t WHERE key = :k;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RAISE_APPLICATION_ERROR(-20190, 'Non-unique key');
END;
/37. TRIGGER: недопустимое время
CREATE OR REPLACE TRIGGER trg_hours
BEFORE INSERT ON shifts
FOR EACH ROW
BEGIN
IF :NEW.start_time >= :NEW.end_time THEN
RAISE_APPLICATION_ERROR(-20200, 'start_time must be < end_time');
END IF;
END;
/38. ORA‑00054 (resource busy) → понятное сообщение
DECLARE
e_timeout EXCEPTION;
PRAGMA EXCEPTION_INIT(e_timeout, -54); -- resource busy
BEGIN
SELECT * INTO :x FROM t WHERE id = :id FOR UPDATE NOWAIT;
EXCEPTION
WHEN e_timeout THEN
RAISE_APPLICATION_ERROR(-20210, 'Row is locked');
END;
/39. Автономный шаг аудита и остановка
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO audit(msg) VALUES ('ping');
COMMIT;
RAISE_APPLICATION_ERROR(-20220, 'Stop after audit');
END;
/40. BULK COLLECT: пустой набор — сигнал вызывающему коду
DECLARE
TYPE t IS TABLE OF NUMBER;
v t;
BEGIN
SELECT id BULK COLLECT INTO v FROM t WHERE status = 'NEW';
IF v.COUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20230, 'Nothing to process');
END IF;
END;
/Еще 20 примеров.
41. Парсинг JSON: ошибка формата
DECLARE
v_obj JSON_OBJECT_T;
BEGIN
v_obj := JSON_OBJECT_T.parse(:txt);
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20240, 'Invalid JSON: '||SQLERRM);
END;
/42. UTL_FILE: не удалось открыть файл
DECLARE
f UTL_FILE.FILE_TYPE;
BEGIN
f := UTL_FILE.FOPEN('TMP_DIR','x.txt','r');
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20250, 'File error: '||SQLERRM);
END;
/43. Пустой результат бизнес‑запроса
BEGIN
SELECT COUNT(*) INTO :cnt FROM orders WHERE status = 'READY';
IF :cnt = 0 THEN
RAISE_APPLICATION_ERROR(-20260, 'No ready orders');
END IF;
END;
/44. Валидация входных параметров API
CREATE OR REPLACE PROCEDURE api_do(p_id NUMBER, p_mode VARCHAR2) IS
BEGIN
IF p_id IS NULL OR p_mode IS NULL THEN
RAISE_APPLICATION_ERROR(-20270, 'p_id and p_mode required');
END IF;
END;
/45. Проверка e‑mail по REGEXP_LIKE
BEGIN
IF NOT REGEXP_LIKE(:email, '^[^@]+@[^@]+\.[^@]+$') THEN
RAISE_APPLICATION_ERROR(-20280, 'Invalid email');
END IF;
END;
/46. Дубли перед вставкой — защитная проверка
BEGIN
IF EXISTS (SELECT 1 FROM users WHERE email = :email) THEN
RAISE_APPLICATION_ERROR(-20290, 'Email already used');
END IF;
END;
/47. MERGE и пост‑проверка доменных условий
MERGE INTO accounts a
USING (SELECT :id id, :amt amt FROM dual) s
ON (a.id = s.id)
WHEN MATCHED THEN UPDATE SET a.balance = a.balance + s.amt
WHEN NOT MATCHED THEN INSERT (id,balance) VALUES (s.id, s.amt);
BEGIN
IF :amt < 0 THEN
RAISE_APPLICATION_ERROR(-20300, 'Negative amount');
END IF;
END;
/48. GTT очистка — отсутствие строк как сигнал
CREATE GLOBAL TEMPORARY TABLE tmp (id NUMBER) ON COMMIT DELETE ROWS;
BEGIN
DELETE FROM tmp;
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20310, 'Nothing to clear');
END IF;
END;
/49. Контроль сеансового контекста
BEGIN
IF SYS_CONTEXT('USERENV','MODULE') IS NULL THEN
RAISE_APPLICATION_ERROR(-20320, 'MODULE not set');
END IF;
END;
/50. DUP_VAL_ON_INDEX → понятное сообщение
BEGIN
INSERT INTO u(id) VALUES (:id);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
RAISE_APPLICATION_ERROR(-20330, 'Duplicate key');
END;
/51. ORA‑01031 (недостаточно прав) → бизнес‑код
DECLARE
e_priv EXCEPTION;
PRAGMA EXCEPTION_INIT(e_priv, -1031);
BEGIN
EXECUTE IMMEDIATE 'ALTER SYSTEM SWITCH LOGFILE';
EXCEPTION
WHEN e_priv THEN
RAISE_APPLICATION_ERROR(-20340, 'Insufficient privileges');
END;
/52. Проверка длины параметра
BEGIN
IF :name IS NULL OR LENGTH(:name) < 3 THEN
RAISE_APPLICATION_ERROR(-20350, 'Name too short');
END IF;
END;
/53. Режим обслуживания — запрет операций
BEGIN
IF EXISTS (SELECT 1 FROM flags WHERE key='MAINTENANCE' AND val='ON') THEN
RAISE_APPLICATION_ERROR(-20360, 'Maintenance mode');
END IF;
END;
/54. Диапазон даты: from > to — ошибка
BEGIN
IF :d_from > :d_to THEN
RAISE_APPLICATION_ERROR(-20370, 'Invalid date range');
END IF;
END;
/55. ORA‑08177 (сериализация) → сигнал наверх
DECLARE
e_ser EXCEPTION;
PRAGMA EXCEPTION_INIT(e_ser, -8177);
BEGIN
-- сериализуемая транзакция
EXCEPTION
WHEN e_ser THEN
RAISE_APPLICATION_ERROR(-20380, 'Serialization failure');
END;
/56. Компенсация и повторный проброс
BEGIN
BEGIN
-- шаг 1
NULL;
-- шаг 2
NULL;
EXCEPTION
WHEN OTHERS THEN
-- возврат ресурсов
NULL;
RAISE; -- отдать ошибку вызывающему коду
END;
END;
/57. Запрет «reopen» закрытого статуса
BEGIN
IF :old_status = 'CLOSED' AND :new_status = 'OPEN' THEN
RAISE_APPLICATION_ERROR(-20390, 'Cannot reopen closed');
END IF;
END;
/58. Optimistic locking: версия не сходится
BEGIN
UPDATE t SET val = :val, version = version + 1
WHERE id = :id AND version = :ver;
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20400, 'Stale object');
END IF;
END;
/59. Лимит попыток превышен
DECLARE
v_attempts PLS_INTEGER := :attempts;
BEGIN
IF v_attempts > 5 THEN
RAISE_APPLICATION_ERROR(-20410, 'Too many attempts');
END IF;
END;
/60. Проверка ENUM значений
BEGIN
IF :status NOT IN ('NEW','OPEN','DONE') THEN
RAISE_APPLICATION_ERROR(-20420, 'Invalid status');
END IF;
END;
/Еще 20 примеров.
61. Недостаточно средств для оплаты
BEGIN
IF :balance < :price THEN
RAISE_APPLICATION_ERROR(-20430, 'Insufficient funds');
END IF;
END;
/62. Невалидный JWT (пример)
BEGIN
IF NOT verify_jwt(:token) THEN
RAISE_APPLICATION_ERROR(-20440, 'Invalid token');
END IF;
END;
/63. Проверка владельца ресурса
BEGIN
IF :user_id <> :owner_id THEN
RAISE_APPLICATION_ERROR(-20450, 'Not an owner');
END IF;
END;
/64. Не удалось отправить письмо
BEGIN
IF NOT send_mail(:to, :subj, :body) THEN
RAISE_APPLICATION_ERROR(-20460, 'Mail send failed');
END IF;
END;
/65. Досрочное завершение цикла
BEGIN
FOR i IN 1..100 LOOP
IF i = :stop_at THEN
RAISE_APPLICATION_ERROR(-20470, 'Stopped at '||i);
END IF;
END LOOP;
END;
/66. Защищённый объект — удалить нельзя
BEGIN
IF :obj_type = 'SYSTEM' THEN
RAISE_APPLICATION_ERROR(-20480, 'Protected object');
END IF;
END;
/67. Ошибка кодировки входных данных
BEGIN
IF NOT is_valid_encoding(:data) THEN
RAISE_APPLICATION_ERROR(-20490, 'Invalid encoding');
END IF;
END;
/68. Составной ключ — проверка уникальности
BEGIN
IF EXISTS (
SELECT 1 FROM t WHERE a=:a AND b=:b
) THEN
RAISE_APPLICATION_ERROR(-20500, 'Duplicate (a,b)');
END IF;
END;
/69. Пустой список для обработки
BEGIN
IF :list_count = 0 THEN
RAISE_APPLICATION_ERROR(-20510, 'Empty list');
END IF;
END;
/70. Истёк срок действия ресурса
BEGIN
IF :expires_at < SYSTIMESTAMP THEN
RAISE_APPLICATION_ERROR(-20520, 'Expired');
END IF;
END;
/71. Некорректный валютный курс
BEGIN
IF :rate <= 0 THEN
RAISE_APPLICATION_ERROR(-20530, 'Invalid FX rate');
END IF;
END;
/72. Парсинг CSV: нет разделителя
BEGIN
IF INSTR(:line, ';') = 0 THEN
RAISE_APPLICATION_ERROR(-20540, 'CSV delimiter missing');
END IF;
END;
/73. Размер файла превышает лимит
BEGIN
IF :size_mb > 100 THEN
RAISE_APPLICATION_ERROR(-20550, 'File too large');
END IF;
END;
/74. Неподдерживаемое значение флага
BEGIN
IF :flag NOT IN (0,1) THEN
RAISE_APPLICATION_ERROR(-20560, 'Flag must be 0/1');
END IF;
END;
/75. Состояние LOCKED — запрещено
BEGIN
IF :state = 'LOCKED' THEN
RAISE_APPLICATION_ERROR(-20570, 'State is LOCKED');
END IF;
END;
/76. Индекс вне диапазона
DECLARE
TYPE t IS TABLE OF NUMBER;
v t := t(1,2,3);
BEGIN
IF :idx < 1 OR :idx > v.COUNT THEN
RAISE_APPLICATION_ERROR(-20580, 'Index out of bounds');
END IF;
END;
/77. Контрольная сумма не совпала
BEGIN
IF :checksum <> calc_checksum(:data) THEN
RAISE_APPLICATION_ERROR(-20590, 'Checksum mismatch');
END IF;
END;
/78. Сервис недоступен
BEGIN
IF NOT ping_service(:url) THEN
RAISE_APPLICATION_ERROR(-20600, 'Service unavailable');
END IF;
END;
/79. Номер неверного формата
BEGIN
IF NOT REGEXP_LIKE(:num, '^\d{10}$') THEN
RAISE_APPLICATION_ERROR(-20610, 'Invalid number');
END IF;
END;
/80. Недостаточно кредитов
BEGIN
IF :credits < :need THEN
RAISE_APPLICATION_ERROR(-20620, 'Not enough credits');
END IF;
END;
/Еще 20 примеров.
81. Смена роли без прав
BEGIN
IF NOT has_priv(:user, 'ROLE_SWITCH') THEN
RAISE_APPLICATION_ERROR(-20630, 'No permission');
END IF;
END;
/82. Сумма строк заказа не равна итогу
BEGIN
IF :sum_lines <> :total THEN
RAISE_APPLICATION_ERROR(-20640, 'Sum mismatch');
END IF;
END;
/83. Обновление закрытого документа
BEGIN
IF :doc_status = 'CLOSED' THEN
RAISE_APPLICATION_ERROR(-20650, 'Document is closed');
END IF;
END;
/84. Нет обязательного вложения
BEGIN
IF :attachments = 0 THEN
RAISE_APPLICATION_ERROR(-20660, 'Attachment required');
END IF;
END;
/85. Версия API неподдерживаемая
BEGIN
IF :api_version NOT IN ('v1','v2') THEN
RAISE_APPLICATION_ERROR(-20670, 'Unsupported API version');
END IF;
END;
/86. Неверный формат даты
BEGIN
IF NOT REGEXP_LIKE(:d, '^\d{4}-\d{2}-\d{2}$') THEN
RAISE_APPLICATION_ERROR(-20680, 'Invalid date format');
END IF;
END;
/87. Конфликт расписаний
BEGIN
IF overlaps(:start1,:end1,:start2,:end2) THEN
RAISE_APPLICATION_ERROR(-20690, 'Schedule conflict');
END IF;
END;
/88. Домен в чёрном списке
BEGIN
IF EXISTS (SELECT 1 FROM domain_blacklist WHERE domain = :d) THEN
RAISE_APPLICATION_ERROR(-20700, 'Domain blocked');
END IF;
END;
/89. Срок карты истёк
BEGIN
IF :exp < TO_CHAR(SYSDATE,'YYMM') THEN
RAISE_APPLICATION_ERROR(-20710, 'Card expired');
END IF;
END;
/90. Неверный статус для отправки
BEGIN
IF :status NOT IN ('READY','SHIPPED') THEN
RAISE_APPLICATION_ERROR(-20720, 'Bad status for shipping');
END IF;
END;
/91. Пустое сообщение не допускается
BEGIN
IF :txt IS NULL OR TRIM(:txt) = '' THEN
RAISE_APPLICATION_ERROR(-20730, 'Empty text');
END IF;
END;
/92. Чрезмерное число попыток входа
BEGIN
IF :login_attempts > 10 THEN
RAISE_APPLICATION_ERROR(-20740, 'Too many login attempts');
END IF;
END;
/93. OTP недействителен
BEGIN
IF NOT verify_otp(:user, :otp) THEN
RAISE_APPLICATION_ERROR(-20750, 'Invalid OTP');
END IF;
END;
/94. Склад недоступен
BEGIN
IF NOT warehouse_available(:wh) THEN
RAISE_APPLICATION_ERROR(-20760, 'Warehouse offline');
END IF;
END;
/95. Дубликат имени проекта
BEGIN
IF EXISTS (SELECT 1 FROM projects WHERE name = :name) THEN
RAISE_APPLICATION_ERROR(-20770, 'Project already exists');
END IF;
END;
/96. Несогласованная валюта заказа
BEGIN
IF :order_ccy <> :customer_ccy THEN
RAISE_APPLICATION_ERROR(-20780, 'Currency mismatch');
END IF;
END;
/97. Нарушение SLA
BEGIN
IF :elapsed_minutes > :sla_minutes THEN
RAISE_APPLICATION_ERROR(-20790, 'SLA breached');
END IF;
END;
/98. Пустой результат поиска
BEGIN
IF :found = 0 THEN
RAISE_APPLICATION_ERROR(-20800, 'Nothing found');
END IF;
END;
/99. Недопустимый MIME‑тип
BEGIN
IF :mime NOT IN ('image/png','image/jpeg','application/pdf') THEN
RAISE_APPLICATION_ERROR(-20810, 'Unsupported MIME type');
END IF;
END;
/100. Комментарий слишком длинный
BEGIN
IF :len > 1000 THEN
RAISE_APPLICATION_ERROR(-20820, 'Comment too long');
END IF;
END;
/Частые ошибки и подводные камни
- Потеря стека. Замена «голого»
RAISE;наRAISE e;в обработчике изменяет трассировку. - Диапазон кодов.
RAISE_APPLICATION_ERRORдопускает только -20000..-20999. - Проглатывание исключений. Пустой
WHEN OTHERSбез проброса скрывает проблемы. - Транзакции. При неотловленной ошибке выполняется откат до последнего
SAVEPOINTили всей транзакции. - PRAGMA EXCEPTION_INIT. Связывайте коды с именами, избегая «магических чисел».
Альтернативы
- Возвращаемые коды/флаги — проще, но ломают чистоту управления и часто игнорируются.
- Ограничения таблиц (
CHECK) — для простых инвариантов без сложной логики. - Логирование в автономной транзакции и последующий проброс — удобно для мониторинга.
Заключение
RAISE делает обработку ошибок явной и дисциплинированной: проверяйте инварианты, логируйте причины, поднимайте понятные коды и не затирайте стек без необходимости.
Документация
RAISE_APPLICATION_ERROR · Exceptions Overview
Следующая статья:
EXCEPTION в PL/SQL или как написать надёжный код