Skip to Main Content

APEX

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

APEX and DBMS_COMPARISON mysterious behaviour

Pavel_pJan 22 2018 — edited May 25 2018

Hello,

I apologize that this is going to be quite long even if I did my best to simplify things as much as possible. However I recommend to keep on reading as I'm going to reveal here absolutely mysterious things (APEX and DB version is probably not relevant, the behaviour is consistent in all versions).

I tried to synchronize two tables using DBMS_COMPARISON https://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_comparison.htm#CHDHEFFJ  package (source code below). For simplicity I used the well known DEPT table and its replica with a few different rows.

Union all from both tables under the same scheme (TESTING) looks like this:

DEPTNO

DNAME

LOC

TABLE_NAME

10

ACCOUNTING

NEW YORK

DEPT

20

RESEARCH

DALLAS

DEPT

30

SALES

CHICAGO

DEPT

40

OPERATIONS

BOSTON

DEPT

10

ACCOUNTINGxx

NEW YORK

DEPT_REPLICA

20

RESEARCH

DALLAS

DEPT_REPLICA

30

SALES

CHICAGO

DEPT_REPLICA

99

NEW_DEPT

null

DEPT_REPLICA

Now I ran the following code from SQL Developer (source code of the CMP_DEPT package below):

begin

  cmp_dept.prepare_dept_replica; --just creates a copy of the DEPT table named DEPT_REPLICA with the above data

  cmp_dept.g_logmode := cmp_dept.k_logmode_dbms_output; --dbms_output used for debug messages (in APEX is apex_debug.message used instead)

  cmp_dept.sync_tables( --run the table synchronization procedure (in APEX is in P1_CURRENT_SCHEMA filled by expression sys_context ( 'userenv', 'current_schema' )

    p_master_table     => 'DEPT',

    p_master_schema    =>:p1_current_schema,

    p_replica_table    => 'DEPT_REPLICA',

    p_replica_schema   =>:p1_current_schema

  );

end;

Expected output is as follows:

Comparing tables: Master TESTING.DEPT, Replica TESTING.DEPT_REPLICA

Created comparison CMP_20180122_145905

Comparison result:

Scan ID: 202

Differences found: Yes

Converge tables

After converge

Local rows deleted: 0

Remote rows deleted: 1

Local rows merged: 0

Remote rows merged: 2

Recheck tables

Differences found: No

Comparison created, program found differences and synchronized master data to the target table - 1 row deleted, 2 rows merged and finally invoked the RECHECK function just to make sure that data in both tables are identical.

Now let's invoke exactly the same block in SQL Workshop and the mystery begins. Same PL/SQL block but completely different output.

Comparing tables: Master TESTING.DEPT, Replica TESTING.DEPT_REPLICA

Created comparison CMP_20180122_090944

Comparison result:

Scan ID: 196

Differences found: Yes

Converge tables

After converge

Local rows deleted: 0

Remote rows deleted: 0

Local rows merged: 0

Remote rows merged: 0

Recheck tables

Differences found: No

Now some black magic must have happened under the hood, because the original scan found differences, then dbms_comparison.converge did nothing (deleted 0 remote rows and merged 0 remote rows), however the following rescan is quite surprisingly saying that both tables are identical (no differences found) while data in the DEPT_REPLICA remained untouched. Shocking!

Now let's try the same in APEX application (here is used apex_debug.message instead of dbms_output):

begin

  cmp_dept.prepare_dept_replica;

  cmp_dept.g_logmode := cmp_dept.k_logmode_apex_debug;

  cmp_dept.sync_tables(

    p_master_table     => 'DEPT',

    p_master_schema    =>:p1_current_schema,

    p_replica_table    => 'DEPT_REPLICA',

    p_replica_schema   =>:p1_current_schema

  );

end;

Output is as follows:

Comparing tables: Master TESTING.DEPT, Replica TESTING.DEPT_REPLICA

Created comparison CMP_20180122_145632

Comparison result:

Scan ID: 199

Differences found: Yes

Converge tables

After converge

Local rows deleted: 0

Remote rows deleted: 0

Local rows merged: 0

Remote rows merged: 0

Recheck tables

Differences found: No

Exactly the same output and result as from SQL Workshop.

Since I cannot attach here the application export, I created a showcase on apex.oracle.com but I cannot make it working because the CMP_DEPT package requires execute privileges on DBMS_COMPARISON.

It would be highly appreciated if anybody could shed some light into this problem, I've already pulled tons of my hair with no result.

Regards,

Pavel

--------------------------------------------------------

Sample APEX application on https://apex.oracle.com/pls/apex (must be exported with supporting objects)

ws: pp_testing

user: test

pwd: test

package code:

CREATE OR REPLACE package         cmp_dept
as
------------------------------------------------------------------------------------------------------------------------------------------------------------------
--constants
k_logmode_dbms_output constant pls_integer := 1;
k_logmode_apex_debug constant pls_integer := 2;
------------------------------------------------------------------------------------------------------------------------------------------------------------------
--global variables
g_logmode pls_integer range 1 .. 2 := k_logmode_dbms_output;
------------------------------------------------------------------------------------------------------------------------------------------------------------------
--procedures
procedure prepare_dept_replica;

procedure sync_tables(p_master_table   varchar2,
                    p_master_schema  varchar2,
                    p_replica_table  varchar2,
                    p_replica_schema varchar2);

procedure log_message(p_msg varchar2);

procedure drop_user_comparisons;
------------------------------------------------------------------------------------------------------------------------------------------------------------------
end cmp_dept;

/


CREATE OR REPLACE package body         cmp_dept
as
------------------------------------------------------------------------------------------------------------------------------------------------------------------

function bool_to_yesno(p_bool boolean)
return varchar2
is
begin
  case p_bool
    when true then
        return 'Yes';
    when false then
        return 'No';
  end case;
end bool_to_yesno;
------------------------------------------------------------------------------------------------------------------------------------------------------------------

procedure prepare_dept_replica
is
  table_exists exception;
  pragma exception_init (table_exists, -00955);
begin
  --create identical dept table replica
  begin
    execute immediate 'create table dept_replica as select * from dept';
    --primary key needed for comparison
    execute immediate 'alter table dept_replica modify (deptno not null )';
    execute immediate 'alter table dept_replica add constraint dept_replica_pk primary key ( deptno ) enable';
  exception
    when table_exists then --if the table exists...
        --dbms_output.put_line(sqlerrm());
        execute immediate 'truncate table dept_replica';
        execute immediate 'insert into dept_replica select * from dept';
  end;

  --we have identical tables now and we'll make few changes

  execute immediate 'delete from dept_replica where deptno = (select max(deptno) from dept)';--row should be later inserted
  execute immediate q'{update dept_replica set dname = dname || 'xx' where deptno = (select min(deptno) from dept)}';--this one should be merged
  execute immediate q'{insert into dept_replica(deptno,dname) values(99,'NEW_DEPT')}'; --this should be deleted
end prepare_dept_replica;
------------------------------------------------------------------------------------------------------------------------------------------------------------------

procedure sync_tables(p_master_table   varchar2,
                    p_master_schema  varchar2,
                    p_replica_table  varchar2,
                    p_replica_schema varchar2)
as

  l_cmp_result               boolean;
  l_scan_info                dbms_comparison.comparison_type;
  k_comparison_name constant varchar2(20) := 'CMP_' || to_char(sysdate, 'yyyymmdd_hh24miss');
begin
  log_message('Comparing tables: Master ' || p_master_schema || '.' || p_master_table || ', Replica ' || p_replica_schema || '.' || p_replica_table);

  --create comparison
  dbms_comparison.create_comparison(comparison_name => k_comparison_name,
  schema_name => p_master_schema,
  object_name => p_master_table,
  dblink_name => null,
  remote_schema_name => p_replica_schema,
  remote_object_name => p_replica_table);

  log_message('Created comparison ' || k_comparison_name);
  --run comparison
  l_cmp_result := dbms_comparison.compare(comparison_name => k_comparison_name,
  scan_info => l_scan_info,
  perform_row_dif => true);

  log_message('Comparison result:');
  log_message('Scan ID: ' || l_scan_info.scan_id);
  log_message('Differences found: ' || bool_to_yesno(not l_cmp_result));
  if
    l_cmp_result = false --differences found
  then
    --converge
    log_message('Converge tables');
    dbms_comparison.converge(comparison_name => k_comparison_name,
    scan_id => l_scan_info.scan_id,
    scan_info => l_scan_info,
    converge_options => dbms_comparison.cmp_converge_local_wins, -- Default
    perform_commit => true);

    log_message('After converge');
    log_message('Local rows deleted: ' || l_scan_info.loc_rows_deleted);
    log_message('Remote rows deleted: ' || l_scan_info.rmt_rows_deleted);
    log_message('Local rows merged: ' || l_scan_info.loc_rows_merged);
    log_message('Remote rows merged: ' || l_scan_info.rmt_rows_merged);

    --now make sure tables are identical
    log_message('Recheck tables');
    l_cmp_result := dbms_comparison.recheck(
    comparison_name => k_comparison_name,
    scan_id => l_scan_info.scan_id,
    perform_row_dif => true);

    log_message('Differences found: ' || bool_to_yesno(not l_cmp_result));
  end if;

end sync_tables;
------------------------------------------------------------------------------------------------------------------------------------------------------------------

procedure log_message(p_msg varchar2)
as
begin
  case g_logmode
    when k_logmode_dbms_output then
        dbms_output.put_line(p_msg);
    when k_logmode_apex_debug then
        apex_debug.message('DEBUGMSG: %s', p_msg); --add prefix to easily filter debug messages in apex debug trace
  end case;
end log_message;
------------------------------------------------------------------------------------------------------------------------------------------------------------------

procedure drop_user_comparisons
as
begin
  for r in (select comparison_name
      from user_comparison) loop
    --purge it first (probably unneeded)
    dbms_comparison.purge_comparison(r.comparison_name);
    dbms_comparison.drop_comparison(r.comparison_name);
  end loop;
end drop_user_comparisons;

end cmp_dept;

Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Jun 22 2018
Added on Jan 22 2018
5 comments
469 views