Несколько ограничений в таблице: как получить все нарушения?

У меня есть таблица в Oracle с несколькими ограничениями. Когда я вставляю новую запись, и не все ограничения действительны, тогда Oracle поднимает только "первую" ошибку. Как получить все нарушения моей записи?

CREATE TABLE A_TABLE_TEST (
  COL_1 NUMBER NOT NULL,
  COL_2 NUMBER NOT NULL,
  COL_3 NUMBER NOT NULL,
  COL_4 NUMBER NOT NULL
);

INSERT INTO A_TABLE_TEST values (1,null,null,2);

ORA-01400: cannot insert NULL into ("USER_4_8483C"."A_TABLE_TEST"."COL_2")

Я хотел бы получить что-то вроде этого:

Column COL_2: cannot insert NULL
Column COL_3: cannot insert NULL

This would be also sufficient:
Column COL_2: not valid
Column COL_3: not valid

Конечно, я мог бы написать триггер и проверять каждый столбец отдельно, но мне нравится предпочитать ограничения, а не триггеры, их легче поддерживать и не требуют ручного написания кода.

Любая идея?

Ответ 1

Тем временем я нашел экономное решение с использованием отложенных ограничений:

CREATE TABLE A_TABLE_TEST (
  COL_1 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED,
  COL_2 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED,
  COL_3 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED,
  COL_4 NUMBER NOT NULL DEFERRABLE INITIALLY DEFERRED
);

INSERT INTO A_TABLE_TEST values (1,null,null,2);    

DECLARE
    CHECK_CONSTRAINT_VIOLATED EXCEPTION;
    PRAGMA EXCEPTION_INIT(CHECK_CONSTRAINT_VIOLATED, -2290);

    REF_CONSTRAINT_VIOLATED EXCEPTION;
    PRAGMA EXCEPTION_INIT(REF_CONSTRAINT_VIOLATED , -2292);


    CURSOR CheckConstraints IS
    SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME
    FROM USER_CONSTRAINTS
        JOIN USER_CONS_COLUMNS USING (TABLE_NAME, CONSTRAINT_NAME)
    WHERE TABLE_NAME = 'A_TABLE_TEST'
        AND DEFERRED = 'DEFERRED'
        AND STATUS = 'ENABLED';
BEGIN
    FOR aCon IN CheckConstraints LOOP
    BEGIN
        EXECUTE IMMEDIATE 'SET CONSTRAINT '||aCon.CONSTRAINT_NAME||' IMMEDIATE';
    EXCEPTION
        WHEN CHECK_CONSTRAINT_VIOLATED OR REF_CONSTRAINT_VIOLATED  THEN
        DBMS_OUTPUT.PUT_LINE('Constraint '||aCon.CONSTRAINT_NAME||' at Column '||aCon.COLUMN_NAME||' violated');
    END;
    END LOOP;
END;

Работает с любыми проверочными ограничениями (не только NOT NULL). Проверка FOREIGN KEY Ограничение также должно работать.

Добавление/изменение/удаление ограничений не требует дальнейшего обслуживания.

Ответ 2

Нет простого способа сообщить о всех возможных нарушениях ограничений. Потому что, когда Oracle наткнется на первое нарушение ограничения, дальнейшая оценка невозможна, утверждение завершается с ошибкой, если только это ограничение не отложено, или предложение log errors было включено в оператор DML. Но следует отметить, что предложение log errors не сможет поймать все возможные нарушения ограничений, просто записывает первый.

Как один из возможных способов:

  • создать таблицу exceptions. Это можно сделать, выполнив ora_home/rdbms/admin/utlexpt.sql script. Структура таблицы довольно проста:
  • отключить все ограничения таблицы;
  • выполнить DMLs;
  • включить все ограничения с предложением exceptions into <<exception table name>>. Если вы выполнили utlexpt.sql script, имя табличных исключений будет сохранено в формате exceptions.

Таблица тестов:

create table t1(
  col1 number not null,
  col2 number not null,
  col3 number not null,
  col4 number not null
);

Попробуйте выполнить оператор insert:

insert into t1(col1, col2, col3, col4)
  values(1, null, 2, null);

Error report -
SQL Error: ORA-01400: cannot insert NULL into ("HR"."T1"."COL2")

Отключить все ограничения таблицы:

alter table T1 disable constraint SYS_C009951;     
alter table T1 disable constraint SYS_C009950;     
alter table T1 disable constraint SYS_C009953;     
alter table T1 disable constraint SYS_C009952; 

Повторите попытку с предыдущим запросом insert:

insert into t1(col1, col2, col3, col4)
  values(1, null, 2, null);

1 rows inserted.

commit;

Теперь включите ограничения таблицы и сохраните исключения, если они есть, в таблице exceptions:

alter table T1 enable constraint SYS_C009951 exceptions into exceptions; 
alter table T1 enable constraint SYS_C009950 exceptions into exceptions; 
alter table T1 enable constraint SYS_C009953 exceptions into exceptions; 
alter table T1 enable constraint SYS_C009952 exceptions into exceptions; 

Проверьте таблицу exceptions:

column row_id     format a30;
column owner      format a7;
column table_name format a10;
column constraint format a12;

select *
  from exceptions 

ROW_ID                         OWNER   TABLE_NAME CONSTRAINT 
------------------------------ ------- -------    ------------
AAAWmUAAJAAAF6WAAA             HR      T1         SYS_C009951  
AAAWmUAAJAAAF6WAAA             HR      T1         SYS_C009953

Нарушены два ограничения. Чтобы узнать имена столбцов, просто обратитесь к user_cons_columns представлению словаря данных:

column table_name   format a10;
column column_name  format a7;
column row_id       format a20;

select e.table_name
     , t.COLUMN_NAME
     , e.ROW_ID
  from user_cons_columns t
  join exceptions e
    on (e.constraint = t.constraint_name)


TABLE_NAME COLUMN_NAME ROW_ID             
---------- ----------  --------------------
T1         COL2        AAAWmUAAJAAAF6WAAA   
T1         COL4        AAAWmUAAJAAAF6WAAA

В приведенном выше запросе даются имена столбцов и ряды проблемных записей. Имея ряды под рукой, не должно быть проблем, чтобы найти те записи, которые вызывают нарушение ограничений, исправлять их и снова включать ограничения еще раз.

Вот script, который использовался для создания операторов alter table для включения и отключения ограничений:

column cons_disable format a50
column cons_enable format a72

select 'alter table ' || t.table_name || ' disable constraint '|| 
        t.constraint_name || ';' as cons_disable
     , 'alter table ' || t.table_name || ' enable constraint '|| 
        t.constraint_name || ' exceptions into exceptions;' as cons_enable
  from user_constraints t
where t.table_name = 'T1'
order by t.constraint_type

Ответ 3

Вам понадобится реализовать триггер before-insert, чтобы выполнить все условия, о которых вы заботитесь.

Подумайте о ситуации с точки зрения базы данных. Когда вы выполняете insert, база данных может в основном выполнять две функции: завершите вставку успешно или по какой-то причине потерпите неудачу (обычно это нарушение ограничения).

База данных хочет действовать так быстро, как возможно, и не делать ненужной работы. Как только он обнаружил первое нарушение жалобы, он знает, что запись не входит в базу данных. Таким образом, двигатель с мудростью возвращает ошибку и перестает проверять дальнейшие ограничения. Нет причин для того, чтобы двигатель мог получить полный список нарушений.