Sql 表中的多个约束:如何获取所有冲突?

Sql 表中的多个约束:如何获取所有冲突?,sql,oracle,constraints,Sql,Oracle,Constraints,我在Oracle中有一个表,其中有几个约束。当我插入一条新记录并且并非所有约束都有效时,Oracle只会引发“first”错误。如何获取所有违反我记录的行为 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,

我在Oracle中有一个表,其中有几个约束。当我插入一条新记录并且并非所有约束都有效时,Oracle只会引发“first”错误。如何获取所有违反我记录的行为

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
当然,我可以编写触发器并单独检查每一列,但我更喜欢约束而不是触发器,它们更易于维护,并且不需要手动编写代码


有什么想法吗?

您必须实现一个插入前触发器来循环处理您关心的所有条件

从数据库的角度考虑情况。执行
插入时,数据库基本上可以做两件事:成功完成插入或由于某种原因(通常是违反约束)失败


数据库希望尽可能快地进行,而不是做不必要的工作。一旦发现第一个投诉违规,它就知道该记录不会进入数据库。因此,引擎明智地返回一个错误并停止检查进一步的约束。引擎没有理由获取冲突的完整列表。

没有直接的方法来报告所有可能的约束冲突。因为当Oracle偶然发现第一次违反约束时,不可能进行进一步的计算,所以语句将失败,除非该约束被延迟一次或DML语句中包含了
log errors
子句。但需要注意的是,
log errors
子句不能捕获所有可能的约束冲突,只记录第一个

可能的方法之一是:

  • 创建
    异常
    表。可以通过执行
    ora_home/rdbms/admin/utlexpt.sql
    脚本来完成。表的结构非常简单
  • 禁用所有表约束
  • 执行DMLs
  • 子句中启用带有
    异常的所有约束。如果执行
    utlexpt.sql
    脚本,将要存储的表异常的名称将是
    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; 
    
    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
    
    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;
    
    再次尝试执行先前失败的
    insert
    语句:

    insert into t1(col1, col2, col3, col4)
      values(1, null, 2, null);
    
    1 rows inserted.
    
    commit;
    
    现在,启用表的约束,并在
    异常
    表中存储异常(如果有):

    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; 
    
    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
    
    检查
    异常
    表:

    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; 
    
    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\u cons\u 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
    
    上面的查询为我们提供了列名称和有问题记录的rowid。有了rowid,找到导致约束冲突的记录、修复它们并再次启用约束应该不会有问题

    以下是用于生成用于启用和禁用约束的
    alter table
    语句的脚本:

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

    同时,我发现了一个使用延迟约束的精益解决方案:

    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; 
    
    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
    
    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;
    
    它适用于任何检查约束(不仅
    不为NULL
    )。检查
    外键
    约束也应该起作用


    添加/修改/删除约束不需要任何进一步的维护。

    您的解决方案仅在您单独在数据库中时有效。我想向我的应用程序提供更详细的错误消息,其中该表同时被多个会话使用。禁用/启用约束在全局范围内起作用,我不能在我的应用程序中使用它。@当只有一个数据库用户时,我们不会有太多的麻烦,而是当您批量插入数据时,即使用多个insert语句执行脚本,并且不希望每次遇到约束冲突异常时都返回到脚本编辑。我以为你的案子就是这样的。但是如果您在这方面需要更多的交互性,恐怕您需要编写一些代码,但是只为一个DML操作这样做会很奇怪,而且肯定会大大降低速度和扩展性。最好保持约束,允许Oracle一次向用户报告一个错误。在我的问题中,我明确要求提供一个不基于触发器的解决方案。