RAISE в PL/SQL — как управлять логикой ошибок

🟢 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;
/

Частые ошибки и подводные камни

  1. Потеря стека. Замена «голого» RAISE; на RAISE e; в обработчике изменяет трассировку.
  2. Диапазон кодов. RAISE_APPLICATION_ERROR допускает только -20000..-20999.
  3. Проглатывание исключений. Пустой WHEN OTHERS без проброса скрывает проблемы.
  4. Транзакции. При неотловленной ошибке выполняется откат до последнего SAVEPOINT или всей транзакции.
  5. PRAGMA EXCEPTION_INIT. Связывайте коды с именами, избегая «магических чисел».

Альтернативы

  • Возвращаемые коды/флаги — проще, но ломают чистоту управления и часто игнорируются.
  • Ограничения таблиц (CHECK) — для простых инвариантов без сложной логики.
  • Логирование в автономной транзакции и последующий проброс — удобно для мониторинга.

Заключение

RAISE делает обработку ошибок явной и дисциплинированной: проверяйте инварианты, логируйте причины, поднимайте понятные коды и не затирайте стек без необходимости.

Документация

PL/SQL — Statement RAISE

RAISE_APPLICATION_ERROR · Exceptions Overview


🔜 Следующая статья:

EXCEPTION в PL/SQL или как написать надёжный код


Понравилась статья? Поделиться с друзьями: