web-dev-qa-db-fra.com

Oracle: comment UPSERT (mettre à jour ou insérer dans une table?)

L'opération UPSERT met à jour ou insère une ligne dans une table, selon que la table possède déjà une ligne correspondant aux données:

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

Comme Oracle n'a pas d'instruction UPSERT spécifique, quel est le meilleur moyen de le faire?

256
Mark Harrison

Une alternative à MERGE (à l'ancienne):

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;   
45
Tony Andrews

L'instruction MERGE fusionne les données entre deux tables. L'utilisation de DUAL Nous permet d'utiliser cette commande. Notez que ceci n'est pas protégé contre les accès simultanés.

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
195
Mark Harrison

Le double exemple ci-dessus, qui est en PL/SQL, était excellent parce que je voulais faire quelque chose de similaire, mais je le voulais côté client ... donc voici le SQL que j'avais l'habitude d'envoyer une déclaration similaire directement à partir de 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" )

Toutefois, d’un point de vue C #, il est plus lent que de faire la mise à jour et de voir si les lignes affectées sont nulles et d’effectuer l’insertion, le cas échéant.

93
MyDeveloperDay

Une autre alternative sans vérification d'exception:

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;
44
Brian Schmitt
  1. insérer si non existe 
  2. mettre à jour:
     
 INSERER DANS mytable (id1, t1) 
 SÉLECTIONNER 11, 'x1' FROM DUAL 
 WHERE NOT EXISTS (SÉLECTIONNER id1 DANS mytble WHERE id1 = 11); 

 UPDATE ma table SET t1 = 'x1' OERE id1 = 11; .__
22
test1

Aucune des réponses données jusqu’à présent n’est safe face aux accès simultanés, comme indiqué dans le commentaire de Tim Sylvester, et soulèvera des exceptions en cas de course. Pour résoudre ce problème, le combo insertion/mise à jour doit être encapsulé dans une sorte d’instruction de boucle, afin que, dans le cas d’une exception, tout soit répété.

À titre d'exemple, voici comment le code de Grommit peut être encapsulé dans une boucle pour le rendre sûr lorsqu'il est exécuté simultanément:

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; 

N.B. En mode de transaction SERIALIZABLE, ce que je ne recommande pas, vous pourriez être confronté à ce problème ORA-08177: impossible de sérialiser l'accès pour cette transaction exceptions à la place.

22

J'aimerais une réponse de Grommit, sauf que cela nécessite des valeurs de dupe. J'ai trouvé une solution qui peut apparaître une fois: http://forums.devshed.com/showpost.php?p=1182653&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); 
18
Hubbitus

Une note concernant les deux solutions suggérant:

1) Insérer, si exception puis mettre à jour,

ou

2) Mise à jour, si sql% rowcount = 0, insérer

La question de savoir s'il faut d'abord insérer ou mettre à jour dépend également de l'application. Vous attendez-vous plus d'inserts ou plus de mises à jour? Celui qui a le plus de chances de réussir devrait commencer. 

Si vous choisissez le mauvais, vous obtiendrez un tas de lectures d'index inutiles. Ce n'est pas une grosse affaire mais c'est quand même quelque chose à considérer.

8
AnthonyVO

J'utilise le premier échantillon de code depuis des années. Avis non trouvé plutôt que compter.

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;

Le code ci-dessous est le code éventuellement nouveau et amélioré.

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)

Dans le premier exemple, la mise à jour effectue une recherche d'index. Il doit le faire pour mettre à jour la bonne ligne. Oracle ouvre un curseur implicite et nous l'utilisons pour envelopper un insert correspondant afin de savoir que l'insertion ne se produira que lorsque la clé n'existe pas. Mais l'insert est une commande indépendante et il doit effectuer une seconde recherche. Je ne connais pas le fonctionnement interne de la commande de fusion, mais comme il s'agit d'une seule unité, Oracle peut exécuter la bonne insertion ou la mise à jour avec une seule recherche d'index.

Je pense que la fusion est préférable lorsque certains traitements doivent être effectués, ce qui signifie qu’il faut prendre des données de certaines tables et mettre à jour une table, éventuellement insérer ou supprimer des lignes. Mais pour le cas d'une seule ligne, vous pouvez considérer le premier cas car la syntaxe est plus courante.

7
Arturo Hernandez

Copiez et collez l'exemple pour passer d'une table à une autre avec 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

Résultat:

  1. b 4 5 
  2. c 3 3 
  3. un 1 1
0
Bechyňák Petr