Collection with additional column to %ROWTYPE columns (two-way MINUS comparing two data sets)

Viewed 49

I am looking for a way to create collection that would have one additional column that the source table/view columns referred by %ROWTYPE for simplicity.

TYPE my_type IS RECORD ( mode varchar2(10), some_table%ROWTYPE );

Goal:

I want to compare a table and its source view with a two-way MINUS operation and read source view once:

select * from (
select 'INSERT' as mode, a.* from (
select ... from aView
MINUS
select ... from aTable ) a
UNION ALL
select 'DELETE' as mode, b.* from (
select ... from aTable
MINUS
select ... from aView) b
)

I am starting to think of creating global temporary tables (GTTs) for INSERT and DELETE mode performed with INSERT ALL, but I have 25 tables, so that will add 50 GTTs :-/

INSERT ALL
WHEN mode=INSERT THEN INTO inserts_GTT01_tbl VALUES(...)
WHEN mode=DELETE THEN INTO deletes_GTT01_tbl VALUES(...)
SELECT * FROM two-way-minus-view
1 Answers

I hope I understood correctly your predicament, although it is possible I might miss something. If I got your idea correctly, you want to operate over a set of tables and generate the records to delete and the ones to insert based on the minus-two-way.

I would go for global temporary tables, using commit on preserve rows, but I would centralize the code in a procedure

First a control table

create table control_process 
( id_time timestamp default systimestamp , 
  source_table varchar2(128), 
  source_view varchar2(128), 
  rows_processed number, 
  exit_code number, 
  error_message varchar2(400) 
); 

Procedure for GTTs using input parameters table and view

It is very important that you adapt the code below to your needs:

  • If the columns in the table and view are not the same, you must adapt the query behind the GTT creation to use whatever is necessary. If you want to do it dynamically, you must create another cursor to match the columns between the table and view used as input parameter.
  • In order to delete/insert you have to compare the GTT with the original table. That is why I have the cursor with the columns that I use to create the dynamic construction, both for insert and delete.
  • I did not have a place to verify the code, so please be aware that could be typos or errors on it.
  • The control table is optional, but allows you to have a kind of place where you stored the results for each table/view
  • This solution allows you to use it for all the tables and views in one single place.
  • the materialize hint will help you as you read once the view and the table in the construction of the minus-two-way.

Having said that, that could be an approach ( you have to adapt it to your needs )

create or replace procedure pr_generate_rows ( psourcetable in varchar2 , psourceview in varchar2 ) 
is 
vddl clob;
vdml clob;
vsql clob;
vcode pls_integer;
verrm varchar2(300);
out_string varchar2(128);
cursor c_tab_columns 
is 
select column_name, count(*) over () tot_rows 
from all_tab_columns where table_name = psourcetable and owner = 'MY_SCHEMA'
order by column_id;
begin
    vddl := ' create global temporary table gtt_'||psourcetable||' on commit preserve rows
              as
              with x 
              as 
              ( select /*+materialize */ * from '||psourceview||' 
              ), 
              y as 
              ( select /*+materialize */ * from '||psourcetable||' 
              )
              select * from (
              select ''INSERT'' as mode, a.* from 
              (
              select ... from x
              MINUS
              select ... from y 
              ) a
              UNION ALL
              select ''DELETE'' as mode, b.* from (
              select ... from y
              MINUS
              select ... from x ) b
              )
             ' ;

    execute immediate vddl;
    
    vdml := ' insert into control_process ( source_table, source_view , rows_processed , exit_code )
              select '''||psourcetable||''' as source_table , 
                     '''||psourceview||'''  as source_view , 
              ( select count(*) from gtt_'||psourcetable||' ) as rows_processed , 
              0 as exit_code from dual
              ' ;
              
    execute immediate vdml ;
    
    -- Perform Insert and Delete over final table 
    
    -- Delete 
    
    vsql := ' delete from '||psourcetable||' 
              where exists 
              ( select 1 from gtt_'||psourcetable||' a join '||psourcetable||' b 
              on ( ';
    for item in c_tab_columns
    loop
        out_string := item.COLUMN_NAME;
        if  c_tab_columns%rowcount = 1 
        then
            vexpression := ' a.'||out_string||' = b.'||out_string||' and ' ;
            dbms_lob.append(vsql,vexpression);
            dbms_lob.append(vsql,''||chr(10)||'');
        elsif c_tab_columns%rowcount < item.tot_rows then
            vexpression := ' a.'||out_string||' = b.'||out_string||' and ' ;
            dbms_lob.append(vsql,vexpression);
            dbms_lob.append(vsql,''||chr(10)||'');
        else
            vexpression := ' a.'||out_string||' = b.'||out_string||' ' ;
            dbms_lob.append(vsql,vexpression);
            dbms_lob.append(vsql,''||chr(10)||'');      
        end if; 
    end loop; 
    dbms_lob.append ( vsql, ')' );  
    dbms_lob.append(vsql,''||chr(10)||'');      
    dbms_lob.append ( vsql, ' where a.mode = ''DELETE'' '); 
    dbms_lob.append ( vsql, ' ) '); 
    
    execute immediate vsql;
    
    -- Insert 
    
    vsql := ' insert /*+ append */ into '||psourcetable||' a 
              select ' ;

    for item in c_tab_columns
    loop
        out_string := item.COLUMN_NAME;
        if  c_tab_columns%rowcount = 1 
        then
            vexpression := ' b.'||out_string||' , ' ;
            dbms_lob.append(vsql,vexpression);
            dbms_lob.append(vsql,''||chr(10)||'');
        elsif c_tab_columns%rowcount < item.tot_rows then
            vexpression := ' b.'||out_string||' , ' ;
            dbms_lob.append(vsql,vexpression);
            dbms_lob.append(vsql,''||chr(10)||'');
        else
            vexpression := ' b.'||out_string||' ' ;
            dbms_lob.append(vsql,vexpression);
            dbms_lob.append(vsql,''||chr(10)||'');      
        end if; 
    end loop; 
    
    dbms_lob.append(vsql,'from gtt_'||psourcetable||' b where b.mode = ''INSERT'' '); 
    
    execute immediate vsql;
    
exception 
when others then 
  vcode := sqlcode;
  verrm := substr(sqlerrm, 1, 300);
  vdml   := ' insert into control_process ( source_table, source_view , rows_processed , exit_code , error_message  ) 
             values ( 
              '''||psourcetable||''' , 
              '''||psourceview||'''  , 
              0, 
              vcode, 
              '''||verrm||'''
              ) ' ;
              
    execute immediate vdml;
    commit;
    raise;
end;
/

Any questions, let me know.

Related