Merge - przypadek synchronizacja danych

Instrukcja MERGE umożliwia za jednym razem wstawianie, aktualizowanie i usuwanie wierszy w tabeli docelowej. Opiszę na poniższym przykładzie synchronizację danych przy użyciu tej instrukcji.

Konfiguracja

Tworzymy dwie tabele. Pierwsza tabela stanowi źródło informacji i jest bieżącą baza pracowników. Dane w tej tabeli mogą ulegać zmianie, pracownicy mogą być dodawani lub usuwani. Natomiast druga tabela jest typową tabelą raportową, która powinna się synchronizować do listy pracowników z tabeli źródłowej.

-- tworzenie tabel
create table emp_source (
      emp_id     number not null
    , first_name varchar2(64)
    , last_name  varchar2(64)
    , mail       varchar2(255)
    , hire_date  date
    , office     varchar2(64)
    , constraint emp_source_pk primary key ( emp_id ) enable
);

create table emp_dest (
      emp_id   number not null
    , office   varchar2(64)
    , result_1 number
    , result_2 number
    , constraint emp_dest_pk primary key ( emp_id ) enable
);
-- zasilenie tabel przykładowymi danymi
insert all
into emp_source values (1010, 'Basil','Hanagan', 'basil.hanagan@company.com', to_date('1996-05-24','YYYY-MM-DD'),'Consulting')
into emp_source values (1020, 'Anneliese','Namdar','anneliese.namdar@company.com',to_date('2001-04-09','YYYY-MM-DD'),'Consulting')
into emp_source values (1030, 'Vernita','Kulik','vernita.kulik@company.com',to_date('2002-04-01','YYYY-MM-DD'),'Consulting')
into emp_source values (1040, 'Sabine','Bonaguidi','sabine.bonaguidi@company.com',to_date('1983-01-29','YYYY-MM-DD'),'Administration')
into emp_source values (1050, 'Beata','Ladieu','beata.ladieu@company.com',to_date('1983-08-15','YYYY-MM-DD'),'Administration')
into emp_source values (1060, 'Annmarie','Esmay','annmarie.esmay@company.com',to_date('1982-12-06','YYYY-MM-DD'),'Administration')
into emp_source values (1070, 'Gisele','Limthong','gisele.limthong@company.com',to_date('1987-09-27','YYYY-MM-DD'),'Research')
into emp_source values (1080, 'Ty','Flournay','ty.flournay@company.com',to_date('1991-04-12','YYYY-MM-DD'),'Research')
into emp_source values (1090, 'Wynona','Tenda','wynona.tenda@company.com',to_date('1986-08-10','YYYY-MM-DD'),'Research')
into emp_source values (2000, 'Beau','Deerman','beau.deerman@company.com',to_date('1987-08-07','YYYY-MM-DD'),'HR')
into emp_source values (2010, 'Antoine','Applonie','antoine.applonie@company.com',to_date('1982-08-30','YYYY-MM-DD'),'HR')

into emp_dest values (1010, 1, 2 )
into emp_dest values (1020, 1, 2 )
into emp_dest values (1030, 1, 2 )
into emp_dest values (1070, 1, 2 )
into emp_dest values (1080, 1, 2 )
into emp_dest values (1090, 1, 2 )

select 1 from dual;
-- zatwierdzamy zmiany
commit;
-- wyniki
select * from emp_source;
select * from emp_dest;

Wstawianie, aktualizowanie

Abu synchronizować dane w tabeli emp_dest musimy zaktualizować istniejące rekordy oraz dodać nowe. Uruchamiamy typowy skrypt z MERGE.

merge into emp_dest d
using emp_source s on (s.emp_id = d.emp_id)
when matched then 
    update set
    d.office = s.office
when not matched then
    insert values (s.emp_id, s.office, null, null)
; 

select * from emp_dest;

Super, dane zostały zaktualizowane, nowe rekordy dodane. Niestety to nie jest pełna synchronizacja. Co będzie jeżeli z tabeli źródłowej emp_source zostanie usunięty jakiś rekord? Czy możemy się zsynchronizować dane przy pomocy instrukcji MERGE?

Jeżeli popatrzymy na definicję, to klauzula DELETE, dotyczy wierszy w tabeli docelowej, które są aktualizowane przez operację scalania.

Super, czyli musimy odpowiednio zmodyfikować nasze zapytanie tak, aby wiedzieć które wiersze są tylko w tabeli docelowej i nie występują w tabeli źródłowej (ponieważ mamy je usunąć). Musimy wobec tego połączyć dane z obu tabel, oznaczyć je w jakiś sposób oraz wybrać te które maja być zaktualizowane i usunięte.

select emp_id    emp_id
     , any_value(office) keep (dense_rank last order by source_id)  as office
     , max(source_id) as  source_id
from ( select emp_id
            , office
            , 1 as source_id
       from emp_dest
       union all
       select emp_id
            , office
            , 2 as source_id
       from emp_source
)
group by emp_id

O co chodzi w tym zapytaniu?

Może najpierw dla czytelności zaktualizujmy jeden rekord i usuńmy inny rekord z tabeli źródłowej

update emp_source set office = 'Science' where emp_id = 2010;

delete emp_source where emp_id = 1090;

i wykonajmy nasz powyższy SELECT:

W SQL oznaczyłem numerami źródła danych (1 - tabela docelowa emp_dest, 2 - tabela źródłowa emp_source). W uzyskanym wyniku zapytania rekordy z SOURCE_ID=2 oznaczają, że rekord o danym EMP_ID jest w obu tabelach, natomiast z SOURCE_ID=1 oznaczają, że rekord o tym EMP_ID jest tylko w tabeli emp_dest. W kolumnie OFFICE mamy tylko wartości z tabeli emp_source (decyduje o tym KEEP). Czyli mamy to o co nam chodziło.

Pełna instrukcja do synchronizacji danych:

merge into emp_dest d
using (
    select emp_id    emp_id
         , any_value(office) keep (dense_rank last order by source_id)  as office
         , max(source_id) as  source_id
    from ( select emp_id
                , office
                , 1 as source_id
           from emp_dest
           union all
           select emp_id
                , office
                , 2 as source_id
           from emp_source
    )
    group by emp_id
    ) s
on (s.emp_id = d.emp_id)
when matched then 
    update set d.office = s.office
    delete where s.source_id = 1    
when not matched then
    insert values (s.emp_id, s.office, null, null)

W którego wyniku otrzymujemy zsynchronizowane wyniki wg identyfikatora EMP_ID.

Wnioski

Mam nadzieję, że tym przykładem przybliżyłem jak wykorzystać MERGE do aktualizacji, wstawiania oraz usuwania danych. Sposób prosty i szybki. Należy zapamiętać, że klauzula DELETE przetwarza tylko te rekordy, które zostały zaktualizowane w klauzuli UPDATE.

0
Subscribe to my newsletter

Read articles from Dariusz Grzywacz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dariusz Grzywacz
Dariusz Grzywacz