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?
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;
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
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.
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;
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; .__
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.
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);
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.
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.
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: