How can I copy records between tables only if they are valid according to check constraints in Oracle?

StackOverflow https://stackoverflow.com/questions/14893545

質問

I don't know if that is possible, but I want to copy a bunch of records from a temp table to a normal table. The problem is that some records may violate check constraints so I want to insert everything that is possible and generate error logs somewhere else for the invalid records.

If I execute:

INSERT INTO normal_table
  SELECT ... FROM temp_table

nothing would be inserted if any record violates any constraint. I could make a loop and manually insert one by one, but I think the performance would be lower.

Ps: if possible, I'd like a solution that works with Oracle 9

役に立ちましたか?

解決

From Oracle 10gR2, you can use the log errors clause:

EXECUTE DBMS_ERRLOG.CREATE_ERROR_LOG('NORMAL_TABLE');
INSERT INTO normal_table
   SELECT ... FROM temp_table
   LOG ERRORS REJECT LIMIT UNLIMITED;

In its simplest form. You can then see what errors you got:

SELECT ora_err_mesg$
FROM err$_normal_table;

More on the CREATE_ERROR_LOG step here.


I think this approach works from 9i, but don't have an instance available to test on, so this is actually run on 11gR2
Update: tested and tweaked (to avoid PLS-00436) in 9i:

declare
    type t_temp_table is table of temp_table%rowtype;
    l_temp_table t_temp_table;
    l_err_code err_table.err_code%type;
    l_err_msg err_table.err_msg%type;
    l_id err_table.id%type;

    cursor c is select * from temp_table;

    error_array exception;
    pragma exception_init(error_array, -24381);
begin
    open c;
    loop
        fetch c bulk collect into l_temp_table limit 100;
        exit when l_temp_table.count = 0;

        begin
            forall i in 1..l_temp_table.count save exceptions
                insert into normal_table
                values l_temp_table(i);
        exception
            when error_array then
                for j in 1..sql%bulk_exceptions.count loop
                    l_id := l_temp_table(sql%bulk_exceptions(j).error_index).id;
                    l_err_code := sql%bulk_exceptions(j).error_code;
                    l_err_msg := sqlerrm(-1 * sql%bulk_exceptions(j).error_code);
                    insert into err_table(id, err_code, err_msg)
                    values (l_id, l_err_code, l_err_msg);
                end loop;
        end;
    end loop;
end;
/

With all your real columns instead of just id, which I've done just for demo purposes:

create table normal_table(id number primary key);
create table temp_table(id number);
create table err_table(id number, err_code number, err_msg varchar2(2000));

insert into temp_table values(42);
insert into temp_table values(42);

Then run the anonymous block above...

select * from normal_table;

        ID
----------
        42

column err_msg format a50
select * from err_table;

        ID   ERR_CODE ERR_MSG                                          
---------- ---------- --------------------------------------------------
        42          1 ORA-00001: unique constraint (.) violated          

This is less satisfactory on a few levels - more coding, slower if you have a lot of exceptions (because of the individual inserts for those), doesn't show which constraint was violated (or any other error details), and won't retain the errors if you rollback - though you could call an autonomous transaction to log it if that was an issue, which I doubt here.

If you have a small enough volume of data to not want to worry about the limit clause you can simplify it a bit:

declare
    type t_temp_table is table of temp_table%rowtype;
    l_temp_table t_temp_table;
    l_err_code err_table.err_code%type;
    l_err_msg err_table.err_msg%type;
    l_id err_table.id%type;

    error_array exception;
    pragma exception_init(error_array, -24381);
begin
    select * bulk collect into l_temp_table from temp_table;

    forall i in 1..l_temp_table.count save exceptions
        insert into normal_table
        values l_temp_table(i);
exception
    when error_array then
        for j in 1..sql%bulk_exceptions.count loop
            l_id := l_temp_table(sql%bulk_exceptions(j).error_index).id;
            l_err_code := sql%bulk_exceptions(j).error_code;
            l_err_msg := sqlerrm(-1 * sql%bulk_exceptions(j).error_code);
            insert into err_table(id, err_code, err_msg)
            values (l_id, l_err_code, l_err_msg);
        end loop;
end;
/

The 9i documentation doesn't seem to be online any more, but this is in a new-features document, and lots of people have written about it - it's been asked about here before too.

他のヒント

If you're specifically interested only in check constraints then one method to think about is to read the definitions of the target check constraints from the data dictionary and apply them as predicates to the query that extracts data from the source table using dynamic sql.

Given:

 create table t1 (
   col1 number check (col1 between 3 and 10))

You can:

select  constraint_name,
        search_condition
from    user_constraints
where   constraint_type = 'C' and
        table_name = 'T1'

The result being:

"SYS_C00226681", "col1 between 3 and 10"

From there it's "a simple matter of coding", as they say, and the method will work on just about any version of Oracle. The most efficient method would probably be to use a multitable insert to direct rows to either the intended target table or to an error logging table based on the result of a CASE statement that applies the check constraint predicates.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top