Il comando MERGE

lunedì 3 dicembre 2012 alle 03:36 | Pubblicato su SQL | 6 commenti
Tag: , ,

La prima volta che ho visto il comando SQL MERGE con un esempio di utilizzo è stato quando ho letto il libro “Expert Oracle Database Architecture. 9i and 10g Programming Techniques and Solutions”; si tratta di  qualche anno fa, non ricordo esattamente quando, perché sul libro, contrariamente a ciò che a volte faccio non ho segnato le date in cui ho iniziato a leggere i vari capitoli. Quello che però ricordo è che mi colpì e mi segnai questo comando come promemoria. Poi credo di averlo studiato un po’, credo anche che all’epoca il database prevalentemente in uso da me fosse il 9i (quindi è decisamente qualche anno fa) perché una delle cose per cui non l0 avevo trovato utile per me era il fatto che sezione “WHEN NOT MATCHED” era obbligatoria. Siccome però ultimamente ho riscoperto l’utilità di questo comando ho deciso di dedicarci  un bel post per decriverlo.

Il comando MERGE, come fa un po’ intuire il nome, serve a unire il risultato di una query su una tabella o una vista in un’altra tabella, ad esempio Tom Kyte fa vedere, a pagina 118 del libro che sopra ho citato, come utilizza il comando per mantere aggiornata una tabellina con delle statistiche. Quindi la situazione tipica di utilizzo di MERGE è quando si hanno due tabelle collegate fra loro da una chiave e si vuole aggiornare un attributo su una prendendo il dato sull’altra tabella. Riporto un caso di test banale:


create table test_a (aid number, atext varchar2(50));
create table test_b (bid number, btext varchar2(50));

insert into test_a values (1,'testoa1');
insert into test_a values (2,'testoa2');

insert into test_b values (1,'testb1');
insert into test_b values (3,'testb3');

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------
 1 testoa1
 2 testoa2

SVILUPPO40@perseo10 > select * from test_b;

BID BTEXT
---------- --------------------------------
 1 testb1
 3 testb3

Per dare un’idea, la tabella TEST_A potrebbe essere una tabella di anagrafiche di articoli di produzione, la tabella TEST_B una tabella con aggiornamenti che possono essere nuovi articoli o articoli modificati. Ecco un esempio di MERGE sulle due tabelle:


SVILUPPO40@perseo10 > merge into test_a ta using
 2 (select bid,btext from test_b) tb
 3 on (tb.bid=ta.aid)
 4 when matched then update set ta.atext=tb.btext
 5 when not matched then insert (aid,atext) values (tb.bid,tb.btext)
 6 ;

2 di righe unite.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testb1
 2 testoa2
 3 testb3

Ora, dalla versione 10 (non ho verificato sulla 10.1 ma dalla documentazione parrebbe gia uguale alla 10.2), la sezione “WHEN NOT MATCHED” non è più obbligatoria come lo era sulla versione 9.2, quindi è possibile fare questa cosa:


SVILUPPO40@perseo10 > rollback;

Rollback completato.

SVILUPPO40@perseo10 > merge into test_a ta using
 2 (select bid,btext from test_b) tb
 3 on (tb.bid=ta.aid)
 4 when matched then update set ta.atext=tb.btext
 5 ;

Unita 1 riga.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- -------------------------------------------
 1 testb1
 2 testoa2

Io in alternativa al MERGE per questi casi sono sempre riuscito a utilizzare un  update semplice, che però non va sempre bene, come in questo esempio:


SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testoa1
 2 testoa2

SVILUPPO40@perseo10 > update test_a ta set atext=(select btext from test_b tb where ta.aid=tb.bid);

Aggiornate 2 righe.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testb1
 2

Per evitare questo caso che raramente è quanto desiderato occorre una clausola WHERE sull’UPDATE, ad esempio:


SVILUPPO40@perseo10 > rollback;

Rollback completato.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testoa1
 2 testoa2

SVILUPPO40@perseo10 > update test_a ta set
 2 atext=(select btext from test_b tb where ta.aid=tb.bid)
 3 where atext is null;

Aggiornate 0 righe.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testoa1
 2 testoa2

Siccome recentemente mi sono imbattuto in un caso in cui mi era comodo il merge (che come prestazioni è superiore all’UPDATE) , non avevo una condizione di WHERE applicabil all’UPDATE, ma non mi serviva la parte “WHEN NOT MATCHED” sono ricorso all’unico trucco che mi è venuto in mente, ovvero l’inserimento di record con dati fasulli e a seguire una DELETE:


SVILUPPO40@perseo10 > rollback;

Rollback completato.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testoa1
 2 testoa2

SVILUPPO40@perseo10 > merge into test_a ta using
 2 (select bid,btext from test_b) tb
 3 on (tb.bid=ta.aid)
 4 when matched then update set ta.atext=tb.btext
 5 when not matched then insert (aid,atext) values (-1,'X-X')
 6 ;

2 di righe unite.

SVILUPPO40@perseo10 > delete from test_a where aid=-1 and atext='X-X';

Eliminata 1 riga.

SVILUPPO40@perseo10 > select * from test_a;

AID ATEXT
---------- --------------------------------------------------
 1 testb1
 2 testoa2

Come ho appena accennato, un buon motivo per ricorrere a questo espediente è che comunque le prestazioni del comando MERGE sono nettamente superiori a quelle dell’UPDATE con la sotto-query.

About these ads

6 commenti »

RSS feed dei commenti a questo articolo. TrackBack URI

  1. Ciao Crustian,
    post come al solito molto utili.

    Grazie,
    Antonio.

    P.S: Pur io ho il blog su wordpress, come fai a mettere il codice in quella forma? Con wordpress standard mi sembra non si possa fare.

  2. Ciao Cristian,
    di seguito lo statement di UPDATE che generalmente utilizzo io, spero possa esserti utile.

    update test_a
    set atext = (select btext
    from test_b
    where test_a.aid = test_b.bid)
    where exists (select null
    from test_b
    where test_a.aid = test_b.bid);

    ATTENZIONE ! performance migliori in presenza di PK o UK nella tabella da dove si estraggono i dati.

    • Ciao Sandro, grazie anche a te, spero di trovare il tempo per fare qualche analisi più approfondita anche sulla tua soluzione.

      • Ciao Cristian,
        se ho ben capito e sono riuscito a ricostruire il discorso, non potresti risolvere con la sola merge agendo nel modo seguente? :

        merge into test_a ta
        using (select bid,btext from test_b) tb
        on (tb.bid=ta.aid)
        when matched then
        update set ta.atext=tb.btext
        delete where not exists (select null
        from test_b
        where ta.aid = tb.bid)

        Ciao
        Alessandro


Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

Crea un sito o un blog gratuitamente presso WordPress.com. | The Pool Theme.
Entries e commenti feeds.

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 71 follower

%d blogger cliccano Mi Piace per questo: