Search code examples
databaseoraclecheck-constraints

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


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


Solution

  • 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.