2009-11-09 4 views
38

Je dois être capable d'exécuter une requête Oracle qui va insérer un nombre de lignes, mais il vérifie également si une clé primaire existe et si c'est le cas, elle ignore cet insert. Quelque chose comme:insert if not exists oracle

INSERT ALL 
    IF NOT EXISTS(SELECT 1 WHERE fo.primary_key='bar') 
    (
     INSERT INTO 
      schema.myFoo fo (primary_key, value1, value2) 
     VALUES 
      ('bar','baz','bat') 
    ), 

    IF NOT EXISTS(SELECT 1 WHERE fo.primary_key='bar1') 
    (
     INSERT INTO 
      schema.myFoo fo (primary_key, value1, value2) 
     VALUES 
      ('bar1','baz1','bat1') 
    ) 
SELECT * FROM schema.myFoo; 

Est-ce possible avec Oracle?

Points bonus si vous pouvez me dire comment faire cela dans PostgreSQL ou MySQL.

+0

J'aime utiliser CTE - Common Table Expressions [Oracle: comment insérer si une ligne n'existe pas ] (http://stackoverflow.com/questions/3841441/oracle-how-to-insert-if-a-row-doesnt-exist/39704301#39704301) – it3xl

Répondre

21

L'instruction est appelée MERGE. Regarde ça, je suis trop paresseux.

attention, cependant, que MERGE est non atomique, ce qui pourrait provoquer l'effet suivant (grâce, Marius):

sess1:

create table t1 (pk int primary key, i int); 
create table t11 (pk int primary key, i int); 
insert into t1 values(1, 1); 
insert into t11 values(2, 21); 
insert into t11 values(3, 31); 
commit; 

SESS2: insert into t1 values(2, 2);

sess1:

MERGE INTO t1 d 
USING t11 s ON (d.pk = s.pk) 
WHEN NOT MATCHED THEN INSERT (d.pk, d.i) VALUES (s.pk, s.i); 

SESS2: commit;

sess1: ORA-00001

+1

Encore une fois, sans bloquer la table (ou la fiche d'abord), il y a une course. Cette méthode nécessite toujours l'utilisation d'une table temporaire. Je ne dirais pas que c'est faux, mais parfois ça peut être trop. –

+0

Non, la fusion doit être atomique. – erikkallen

+1

Oui, MERGE lui-même est atomique. Mais ... Sess1: INSÉRER pk = 1 INTO myFoo; Sess2: Fusionner dans myFoo d UTILISER tmpTable s ON (d.pk = s.pk) ... Sess1: COMMIT; Sess2: ORA-00001; Pour les cas où le nombre de lignes insérées est faible, cela n'a vraiment aucun sens d'utiliser une table temporaire. Tout a son prix, et CREATE TABLE et MERGE ne sont pas bon marché (regardez les verrous/verrous requis et autres). –

5

Si ce code est sur le client, alors vous avez de nombreux déplacements sur le serveur afin d'éliminer cela.

Insérez toutes les données dans une table temporaire, au dire T avec la même structure que myFoo

Puis

insert myFoo 
    select * 
    from t 
     where t.primary_key not in (select primary_key from myFoo) 

Cela devrait fonctionner sur d'autres bases de données ainsi - je l'ai fait sur Sybase

Ce n'est pas le meilleur si très peu de nouvelles données doivent être insérées car vous avez copié toutes les données sur le fil.

+0

Définitivement cleaver +1. Je devrais d'abord créer une table temporaire, mais ce n'est vraiment pas une difficulté terrible. – cwallenpoole

+0

correction * intelligent *. – cwallenpoole

+0

l'insertion de cette façon est SLOW .... BULK COLLECT est une option bien meilleure ... google il :) Il nécessite quelques pl-sql, mais il est infiniment plus rapide qu'un insert aveugle d'un select. – Matt

0

Ceci est une réponse au commentaire publié par erikkallen:

Vous n'avez pas besoin d'une table temporaire. Si vous avez seulement quelques lignes, (SELECT 1 FROM double UNION SELECT 2 FROM dual) fera . Pourquoi votre exemple donnerait-il ORA-0001? Ne fusionnerait-il pas le verrou de mise à jour sur la clé d'index et non continuer jusqu'à ce que Sess1 ait validé ou annulé?- erikkallen

Eh bien, essayez vous-même et me dire si vous obtenez la même erreur ou non:

sess1:

create table t1 (pk int primary key, i int); 
create table t11 (pk int primary key, i int); 
insert into t1 values(1, 1); 
insert into t11 values(2, 21); 
insert into t11 values(3, 31); 
commit; 

SESS2: insert into t1 values(2, 2);

sess1:

MERGE INTO t1 d 
USING t11 s ON (d.pk = s.pk) 
WHEN NOT MATCHED THEN INSERT (d.pk, d.i) VALUES (s.pk, s.i); 

SESS2: commit;

sess1: ORA-00001

+0

Je n'ai pas accès à Oracle, donc je ne peux pas l'essayer, mais je te crois. Je pense, cependant, que cela devrait être considéré comme un bug. – erikkallen

+2

ce comportement est correct sous le niveau d'isolation de transaction validée en lecture, pas un bogue. le comportement de MERGE est entièrement compatible avec le comportement d'une mise à jour qui n'affecte aucune ligne suivie d'une tentative d'insertion. –

+0

@David: Je réalise que ces choses sont équivalentes, mais je me demande combien de personnes le savent. Je ne sais pas, et je m'attendais vraiment à ce que ça fonctionne sans problème.Si je veux la sémantique d'un INSERT qui n'insère aucune ligne, alors une UPDATE, alors j'écris un INSERT et ensuite un UPDATE. – erikkallen

35

arrivé en retard à la fête, mais ...

avec Oracle 11.2.0.1 il y a un soupçon sémantique qui peut le faire: IGNORE_ROW_ON_DUPKEY_INDEX

Exemple:

insert /*+ IGNORE_ROW_ON_DUPKEY_INDEX(customer_orders,pk_customer_orders) */ 
    into customer_orders 
     (order_id, customer, product) 
values ( 1234,  9876, 'K598') 
    ; 

MISE À JOUR: Bien que cette astuce fonctionne (si vous épeler correctement), il y a better approaches qui ne nécessitent pas Oracle 11R2:

Première approche — traduction directe de plus haut indice sémantique:

begin 
    insert into customer_orders 
     (order_id, customer, product) 
    values ( 1234,  9876, 'K698') 
    ; 
    commit; 
exception 
    when DUP_VAL_ON_INDEX 
    then ROLLBACK; 
end; 

Deuxième aproche — un beaucoup plus vite que les deux conseils ci-dessus quand il y a beaucoup de discorde:

begin 
    select count (*) 
    into l_is_matching_row 
    from customer_orders 
    where order_id = 1234 
    ; 

    if (l_is_matching_row = 0) 
    then 
     insert into customer_orders 
      (order_id, customer, product) 
     values ( 1234,  9876, 'K698') 
     ; 
     commit; 
    end if; 
exception 
    when DUP_VAL_ON_INDEX 
    then ROLLBACK; 
end; 
+1

J'aime la deuxième approche car il est clair et facile de comprendre ce que l'on essaie de faire. –

+0

En cherchant une alternative à la façon dont je m'attaquais à cette situation, j'ai trouvé votre réponse qui renforce que mon approche était correcte. J'ai mis en place la deuxième approche, et c'est clair et rapide! +1 –

+0

Un conseil pour ceux qui veulent accéder à des objets créés par SQL statique (pas PL/SQL, qui est SQL dynamique). Par exemple, si vous souhaitez obtenir NEXTVAL à partir de la séquence que vous avez créée en dehors de PL/SQL "BEGIN END"; bloc. Faites "DECLARE seq_value NUMBER;" puis "SELECT employees_seq.NEXTVAL INTO seq_value FROM dual"; https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/sqloperations.htm#i7112 –

16

Insère uniquement si l'élément à insérer n'est pas déjà présent.

fonctionne comme:

if not exists (...) insert ... 

dans T-SQL

insert into destination (DESTINATIONABBREV) 
    select 'xyz' from dual 
    left outer join destination d on d.destinationabbrev = 'xyz' 
    where d.destinationid is null; 

peut-être pas assez, mais il est très pratique :)

+12

Ou similaire, avec existe: 'insérer dans la destination sélectionner 'id', 'xyz' à partir du double où n'existe pas (sélectionnez id de la destination où id = 'id') ' – robinst

+1

solution Robinst (commentaire) est le meilleur à mon avis –

+0

belle robinst ... parfait! merci beaucoup – KingRider

3
DECLARE 
    tmp NUMBER(3,1); 
BEGIN 
    SELECT COUNT(content_id) INTO tmp FROM contents WHERE (condition); 
    if tmp != 0 then 
    INSERT INTO contents VALUES (...); 
    else 
    INSERT INTO contents VALUES (...); 
    end if; 
END; 

J'ai utilisé le code ci-dessus. C'est long, mais simple et travaillé pour moi. Similaire au code de Micheal.

8

Si vous ne voulez pas fusionner à partir d'une autre table, mais plutôt insérer de nouvelles données ... Je suis venu avec cela. Y a-t-il peut-être une meilleure façon de faire cela?

MERGE INTO TABLE1 a 
    USING DUAL 
    ON (a.C1_pk= 6) 
WHEN NOT MATCHED THEN 
    INSERT(C1_pk, C2,C3,C4) 
    VALUES (6, 1,0,1); 
0

Si votre table est « indépendant » des autres (je veux dire, il ne déclenchera pas une suppression en cascade ou ne posez pas de relations clés étrangères null), une astuce pourrait être d'abord supprimer la ligne et puis insérez-le à nouveau.Cela pourrait se passer comme suit:

DELETE FROM MyTable O WH prop1 = 'aaa'; // en supposant qu'il va sélectionner au plus une ligne!

INSCRIRE DANS MyTable (prop1, ...) VALEURS ('aaa', ...);

Si vous supprimez quelque chose qui n'existe pas, rien ne se passera.

+0

est-il un moyen de vérifier si une ligne existe. –

0

 
INSERT INTO schema.myFoo (primary_key  , value1   , value2  ) 
         SELECT 'bar1' AS primary_key ,'baz1' AS value1 ,'bat1' AS value2 FROM DUAL WHERE (SELECT 1 AS value FROM schema.myFoo WHERE LOWER(primary_key) ='bar1' AND ROWNUM=1) is null; 

8

Nous pouvons combiner les DUAL et NOT EXISTS pour archiver vos besoins:

INSERT INTO schema.myFoo ( 
    primary_key, value1, value2 
) 
SELECT 
    'bar', 'baz', 'bat' 
FROM DUAL 
WHERE NOT EXISTS (
    SELECT 1 
    FROM schema.myFoo 
    WHERE primary_key = 'bar' 
); 
Questions connexes