简体   繁体   中英

Find out which Record and Column was Updated Between Two Tables

I have two similar tables (Table_A and Table_B) and They share the same Unique identifier (Column Employee_Number). And they also have similar Data.

Create table Table_A
(
    Employee_Number varchar2(100),
    name            varchar2(100),
    address         varchar2(100),
    tel_no          varchar2(100),
    social_sec_no   varchar2(100)
);

INSERT INTO Table_A  (Employee_Number, name, address, tel_no, social_sec_no) values ('1', 'emp 1', 'home1', '1111', '11111');
INSERT INTO Table_A  (Employee_Number, name, address, tel_no, social_sec_no) values ('2', 'emp 2', 'home2', '2222', '22222');
INSERT INTO Table_A  (Employee_Number, name, address, tel_no, social_sec_no) values ('3', 'emp 3', 'home3', '3333', '33333');
INSERT INTO Table_A  (Employee_Number, name, address, tel_no, social_sec_no) values ('4', 'emp 4', 'home4', '4444', '44444');
INSERT INTO Table_A  (Employee_Number, name, address, tel_no, social_sec_no) values ('5', 'emp 5', 'home5', '5555', '55555');

commit;

create table Table_b
as
select  *
from    Table_A;

However, one of Table B's records got changed:

update  Table_b
set     social_sec_no = '99999'
where   Employee_Number = '1';

update  Table_b
set     social_sec_no = 'xxxx'
where   Employee_Number = '3';

update  Table_b
set     address = 'office'
where   Employee_Number = '1';

commit;

This is what I did to find out what column was changed:

SELECT  *
FROM    (select AX.Employee_Number
              , CASE WHEN AX.name          <> bx.NAME          THEN 'CHANGED' else 'NO_CHANGE' END  name
              , CASE WHEN AX.address       <> bx.address       THEN 'CHANGED' else 'NO_CHANGE' END  address
              , CASE WHEN AX.tel_no        <> bx.tel_no        THEN 'CHANGED' else 'NO_CHANGE' END  tel_no
              , CASE WHEN AX.social_sec_no <> bx.social_sec_no THEN 'CHANGED' else 'NO_CHANGE' END  social_sec_no
        from    Table_A ax
              , Table_B bx
        where   ax.Employee_Number = bx.Employee_Number
        and    (ax.name          <> bx.name
        or      ax.address       <> bx.address
        or      ax.tel_no        <> bx.tel_no
        or      ax.social_sec_no <> bx.social_sec_no))
WHERE   1=1
AND    (name          = 'CHANGED'
OR      address       = 'CHANGED'
OR      tel_no        = 'CHANGED'
OR      social_sec_no = 'CHANGED');   

Result Of the Query

Employee_Number NAME        ADDRESS     TEL_NO      SOCIAL_SEC_NO     
--------------- ---------   ---------   ---------   --------------
1               NO_CHANGE   CHANGED     NO_CHANGE   CHANGED
3               NO_CHANGE   NO_CHANGE   NO_CHANGE   CHANGED       

I was wondering if there's a better and more efficient way to find out which record and column was changed without the use of triggers or any other DDL and DML?

Database Details:

Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production

Thanks!

Variation on a theme... Problem solved many years ago on AskTom, credit to Marco Stefanelli who came up with the core idea.

select * 
from (
  select source, employee_number, name, address, tel_no, social_sec_no,
         count(*) over ( partition by employee_number, 
                       name, address, tel_no, social_sec_no) as cnt
  from (
         select 'table_a' as source, 
                employee_number, name, address, tel_no, social_sec_no
           from table_a
         union all
         select 'table_b' as source, 
                employee_number, name, address, tel_no, social_sec_no
           from table_b
       )
)
where cnt = 1
order by employee_number, source
;

SOURCE   EMPLOYEE_NUMBER NAME       ADDRESS      TEL_NO     SOCIAL_SEC_NO    CNT
-------  --------------- ---------- ------------ ---------- ------------- ------
table_a  1               emp 1      home1        1111       11111              1
table_b  1               emp 1      office       1111       99999              1
table_a  3               emp 3      home3        3333       33333              1
table_b  3               emp 3      home3        3333       xxxx               1

4 rows selected.

This query will identify also the rows in one table that don't have a correspondent (row with the same employee_number ) in the other table. The way it works is, the count(*) is 2 if the exact same row exists in both table.

If you want to exclude the rows that are in one table and not in the other (at all - meaning no matching employee_number ), add another "column" to the middle select , for count(*) over (partition by employee_number) - this will be 1 if an employee_number appears in one table but not in the other, so in the where clause in the outer query ask that this number be = 2 .

A simpler version (a bit harder to "hack" to exclude the rows that don't have a matching employee_number in the other table though):

select max(source) as source, 
       employee_number, name, address, tel_no, social_sec_no
from   (
         select 'table_a' as source, 
                employee_number, name, address, tel_no, social_sec_no
           from table_a
         union all
         select 'table_b' as source, 
                 employee_number, name, address, tel_no, social_sec_no
           from table_b
       )
group by employee_number, name, address, tel_no, social_sec_no
having count(*) = 1
order by employee_number, source
;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM