web-dev-qa-db-fra.com

insérer s'il n'existe pas oracle

J'ai besoin de pouvoir exécuter une requête Oracle qui va insérer un certain nombre de lignes, mais il vérifie également si une clé primaire existe et si c'est le cas, il ignore cette insertion. Quelque chose comme:

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 que cela est possible avec Oracle?

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

42
cwallenpoole

L'instruction s'appelle MERGE. Regardez, je suis trop paresseux.

Attention, cependant, MERGE n'est pas atomique, ce qui pourrait provoquer l'effet suivant (merci, 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

24
erikkallen

Venir en retard à la fête, mais ...

Avec Oracle 11.2.0.1, il existe un indice 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')
     ;

[~ # ~] update [~ # ~] : Bien que cette astuce fonctionne (si vous l'orthographiez correctement), il existe meilleures approches qui ne nécessite pas Oracle 11R2:

Première approche - traduction directe de l'indice sémantique ci-dessus:

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 approche — a beaucoup plus rapide que les deux indices ci-dessus lorsqu'il y a beaucoup de conflits:

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;
36
Michael Deardeuff

Ceci n'est inséré que si l'élément à insérer n'est pas déjà présent.

Fonctionne de la même manière que:

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

en T-SQL

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

n'est peut-être pas joli, mais c'est pratique :)

18
AlanG

Nous pouvons combiner le DUAL et le NON EXISTANT pour archiver votre besoin:

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'
);
10
OnionHead

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 procéder?

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);
10
W_O_L_F

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

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

Ensuite

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

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

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

5
Mark
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. Il est long, mais simple et a fonctionné pour moi. Similaire au code de Micheal.

4
Selin

Voici une réponse au commentaire posté par erikkallen:

Vous n'avez pas besoin d'une table temporaire. Si vous n'avez que quelques lignes, (SELECT 1 FROM dual UNION SELECT 2 FROM dual) fera l'affaire. Pourquoi votre exemple donnerait-il ORA-0001? La fusion ne prendrait-elle pas le verrou de mise à jour sur la clé d'index et ne continuerait-elle pas tant que Sess1 n'a pas validé ou annulé? - erikkallen

Eh bien, essayez vous-même et dites-moi 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
Marius Burz

Si votre table est "indépendante" des autres (je veux dire, elle ne déclenchera pas une suppression en cascade ou ne mettra aucune relation de clé étrangère à null), une astuce intéressante pourrait être de SUPPRIMER d'abord la ligne puis de l'INSÉRER à nouveau. Cela pourrait se passer comme ceci:

SUPPRIMER DE MyTable WHERE prop1 = 'aaa'; // en supposant qu'il sélectionnera au plus une ligne!

INSÉRER DANS MyTable (prop1, ...) VALEURS ('aaa', ...);

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

0
zetzer
 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) est null; 
0
RaZieRSarE