Domanda

L'operazione UPSERT aggiorna o inserisce una riga in una tabella, a seconda che la tabella abbia già una riga che corrisponde ai dati:

if table t has a row exists that has key X:
    update t set mystuff... where mykey=X
else
    insert into t mystuff...

Dato che Oracle non ha una specifica dichiarazione UPSERT, qual è il modo migliore per farlo?

È stato utile?

Soluzione

Un'alternativa a MERGE (la "vecchia maniera"):

begin
   insert into t (mykey, mystuff) 
      values ('X', 123);
exception
   when dup_val_on_index then
      update t 
      set    mystuff = 123 
      where  mykey = 'X';
end;   

Altri suggerimenti

La Dichiarazione MERGE unisce i dati tra due tavoli. Utilizzando DUAL ci permette di usare questo comando. Si noti che questo non è protetto dall'accesso simultaneo.

create or replace
procedure ups(xa number)
as
begin
    merge into mergetest m using dual on (a = xa)
         when not matched then insert (a,b) values (xa,1)
             when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;

A                      B
---------------------- ----------------------
10                     2
20                     1

Il doppio esempio sopra riportato in PL / SQL è stato ottimo perché volevo fare qualcosa di simile, ma lo volevo sul lato client ... quindi ecco l'SQL che ho usato per inviare un'istruzione simile direttamente da alcuni C #

MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name") 
    VALUES ( 2097153,"smith", "john" )

Tuttavia, dal punto di vista C #, ciò risulta più lento rispetto all'aggiornamento e alla visualizzazione se le righe interessate erano 0 e all'inserimento se lo fosse.

Un'altra alternativa senza controllo delle eccezioni:

UPDATE tablename
    SET val1 = in_val1,
        val2 = in_val2
    WHERE val3 = in_val3;

IF ( sql%rowcount = 0 )
    THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;
  1. inserisci se non esiste
  2. aggiornamento:
    
INSERT INTO mytable (id1, t1) 
  SELECT 11, 'x1' FROM DUAL 
  WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11); 

UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;

Nessuna delle risposte fornite finora è sicura di fronte ad accessi simultanei , come sottolineato nel commento di Tim Sylvester, e solleverà eccezioni in caso di gare. Per risolvere il problema, la combinazione di inserimento / aggiornamento deve essere racchiusa in una sorta di istruzione loop, in modo che in caso di eccezione venga riprovato il tutto.

Ad esempio, ecco come il codice di Grommit può essere racchiuso in un ciclo per renderlo sicuro quando eseguito contemporaneamente:

PROCEDURE MyProc (
 ...
) IS
BEGIN
 LOOP
  BEGIN
    MERGE INTO Employee USING dual ON ( "id"=2097153 )
      WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
      WHEN NOT MATCHED THEN INSERT ("id","last","name") 
        VALUES ( 2097153,"smith", "john" );
    EXIT; -- success? -> exit loop
  EXCEPTION
    WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
      NULL; -- exception? -> no op, i.e. continue looping
    WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
      NULL; -- exception? -> no op, i.e. continue looping
  END;
 END LOOP;
END; 

NB. Nella modalità di transazione SERIALIZZABILE , che non consiglio a proposito, potresti imbatterti ORA-08177: non è invece possibile serializzare l'accesso per questa transazione eccezioni.

Vorrei che Grommit rispondesse, tranne per il fatto che richiede valori duplicati. Ho trovato una soluzione dove potrebbe apparire una volta: http://forums.devshed.com/ showpost.php p = 1182653 & amp;? postcount = 2

MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
    SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
    FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
    UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
    INSERT (  CILT,   SAYFA,   KUTUK,   MERNIS_NO)
    VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); 

Una nota relativa alle due soluzioni che suggeriscono:

1) Inserisci, se eccezione quindi aggiorna,

o

2) Aggiorna, se sql% rowcount = 0 quindi inserisci

La domanda se inserire o aggiornare prima dipende anche dall'applicazione. Ti aspetti più inserti o più aggiornamenti? Quello che molto probabilmente avrà successo dovrebbe andare per primo.

Se scegli quella sbagliata otterrai un sacco di letture dell'indice non necessarie. Non è un grosso problema, ma ancora qualcosa da considerare.

Uso il primo esempio di codice da anni. Avviso non fondato anziché contare.

UPDATE tablename SET val1 = in_val1, val2 = in_val2
    WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;

Il codice seguente è forse il codice nuovo e migliorato

MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT 
    VALUES (in_val1, in_val2, in_val3)

Nel primo esempio l'aggiornamento esegue una ricerca dell'indice. Deve, per aggiornare la riga giusta. Oracle apre un cursore implicito e lo usiamo per avvolgere un inserto corrispondente, quindi sappiamo che l'inserimento avverrà solo quando la chiave non esiste. Ma l'inserimento è un comando indipendente e deve eseguire una seconda ricerca. Non conosco il funzionamento interno del comando di unione ma poiché il comando è una singola unità, Oracle potrebbe aver eseguito l'inserimento o l'aggiornamento corretti con una singola ricerca dell'indice.

Penso che l'unione sia migliore quando hai qualche elaborazione da fare, questo significa prendere i dati da alcune tabelle e aggiornare una tabella, possibilmente inserendo o eliminando le righe. Ma per il caso a riga singola, puoi considerare il primo caso poiché la sintassi è più comune.

Copia & amp; incolla esempio per invertire una tabella in un'altra, con MERGE:

CREATE GLOBAL TEMPORARY TABLE t1
    (id VARCHAR2(5) ,
     value VARCHAR2(5),
     value2 VARCHAR2(5)
     )
  ON COMMIT DELETE ROWS;

CREATE GLOBAL TEMPORARY TABLE t2
    (id VARCHAR2(5) ,
     value VARCHAR2(5),
     value2 VARCHAR2(5))
  ON COMMIT DELETE ROWS;
ALTER TABLE t2 ADD CONSTRAINT PK_LKP_MIGRATION_INFO PRIMARY KEY (id);

insert into t1 values ('a','1','1');
insert into t1 values ('b','4','5');
insert into t2 values ('b','2','2');
insert into t2 values ('c','3','3');


merge into t2
using t1
on (t1.id = t2.id) 
when matched then 
  update set t2.value = t1.value,
  t2.value2 = t1.value2
when not matched then
  insert (t2.id, t2.value, t2.value2)  
  values(t1.id, t1.value, t1.value2);

select * from t2

Risultato:

  1. b 4 5
  2. c 3 3
  3. a 1 1

Prova questo

insert into b_building_property (
  select
    'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
  from dual
)
minus
(
  select * from b_building_property where id = 9
)
;

Da http://www.praetoriate.com/oracle_tips_upserts.htm :

" In Oracle9i, un UPSERT può eseguire questa attività in una singola istruzione: "

INSERT
FIRST WHEN
   credit_limit >=100000
THEN INTO
   rich_customers
VALUES(cust_id,cust_credit_limit)
   INTO customers
ELSE
   INTO customers SELECT * FROM new_customers;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top