Oracle SQL: mettre à jour une table avec les données d'une autre table

251

Tableau 1:

id    name    desc
-----------------------
1     a       abc
2     b       def
3     c       adf

Tableau 2:

id    name    desc
-----------------------
1     x       123
2     y       345

Dans Oracle SQL, comment exécuter une requête de mise à jour SQL qui peut mettre à jour le tableau 1 avec le tableau 2 nameet en descutilisant le même id? Donc, le résultat final que j'obtiendrais est

Tableau 1:

id    name    desc
-----------------------
1     x       123
2     y       345
3     c       adf

La question provient de la mise à jour d'une table avec les données d'une autre , mais spécifiquement pour Oracle SQL.

Muhd
la source
Vous devez revenir à votre autre question, refuser cette réponse et indiquer spécifiquement que vous avez besoin de la syntaxe Oracle PLSQL.
p.campbell
3
@ p.campbell, ce n'est pas ma question ...
Muhd
1
Oh je vois. Vous avez donc copié-collé le corps de la question, mais modifié pour inclure le bit Oracle.
p.campbell
2
Ouais. Et ce n'est probablement pas le meilleur exemple puisque "desc" est un mot réservé, mais bon.
Muhd

Réponses:

512

C'est ce qu'on appelle une mise à jour corrélée

UPDATE table1 t1
   SET (name, desc) = (SELECT t2.name, t2.desc
                         FROM table2 t2
                        WHERE t1.id = t2.id)
 WHERE EXISTS (
    SELECT 1
      FROM table2 t2
     WHERE t1.id = t2.id )

En supposant que les résultats de la jointure dans une vue préservée par clé, vous pouvez également

UPDATE (SELECT t1.id, 
               t1.name name1,
               t1.desc desc1,
               t2.name name2,
               t2.desc desc2
          FROM table1 t1,
               table2 t2
         WHERE t1.id = t2.id)
   SET name1 = name2,
       desc1 = desc2
Justin Cave
la source
8
Dans votre premier exemple de code: la clause WHERE externe est-elle nécessaire pour obtenir des résultats corrects? Ou l'utilisez-vous uniquement pour accélérer la requête?
Mathias Bader
41
@totoro - Dans le premier exemple, le WHERE EXISTSvous empêche de mettre à jour une ligne dans t1s'il n'y a pas de ligne correspondante t2. Sans cela, chaque ligne t1sera mise à jour et les valeurs seront définies NULLs'il n'y a pas de ligne correspondante t2. Ce n'est généralement pas ce que vous voulez, donc WHERE EXISTSc'est généralement nécessaire.
Justin Cave
3
Il convient d'ajouter que le résultat SELECT ... FROM t2 doit être unique. Cela signifie que vous devez sélectionner tous les champs qui composent une clé unique - une clé primaire non unique n'est pas suffisante. Sans unicité, vous êtes réduit à quelque chose comme la boucle de @ PaulKarr - et s'il n'y a pas de corrélation unique, alors plus d'une ligne cible peut être mise à jour pour chaque ligne source.
Andrew Leach
2
Explication sur l'exigence de clé préservée pour les jointures pouvant être mises à jour: asktom.oracle.com/pls/asktom/…
Vadzim
1
@RachitSharma - Cela signifie que votre sous-requête (la requête de table2) renvoie plusieurs lignes pour une ou plusieurs table1valeurs et Oracle ne sait pas laquelle vous souhaitez utiliser. Normalement, cela signifie que vous devez affiner la sous-requête afin qu'elle renvoie une seule ligne distincte.
Justin Cave
132

Essaye ça:

MERGE INTO table1 t1
USING
(
-- For more complicated queries you can use WITH clause here
SELECT * FROM table2
)t2
ON(t1.id = t2.id)
WHEN MATCHED THEN UPDATE SET
t1.name = t2.name,
t1.desc = t2.desc;
Adrian
la source
4
Très rapide en effet, 1159477 lignes fusionnées en 15,5s
jefissu
3
J'espère que tout le monde visitant cette question après 2015 remarquera cette réponse. Notez que cela fonctionne également si table1et table2sont la même table, il suffit de prendre soin de ON-part et de la WHEREclause-pour la SELECTdéclaration de table2!
sjngm
1
Je trouve que chaque fois que je dois refaire une fusion, je reviens toujours à cette réponse pour trouver l'inspiration. Je pourrais l'imprimer et l'encadrer sur mon mur
arnehehe
Fonctionne comme un charme !! THX!
davidwillianx
SÉLECTIONNEZ L'ID DISTINCT, FIELD1, FIELD1 DE la table2 O ID L'ID N'EST PAS NUL
Joseph Poirier
17

essayer

UPDATE Table1 T1 SET
T1.name = (SELECT T2.name FROM Table2 T2 WHERE T2.id = T1.id),
T1.desc = (SELECT T2.desc FROM Table2 T2 WHERE T2.id = T1.id)
WHERE T1.id IN (SELECT T2.id FROM Table2 T2 WHERE T2.id = T1.id);
Yahia
la source
4
L'inconvénient est que l'instruction SELECT est répétée 3 fois. Dans des exemples complexes, cela peut être une rupture.
David Balažic
9
Update table set column = (select...)

n'a jamais fonctionné pour moi car l'ensemble n'attend qu'une valeur - Erreur SQL: ORA-01427: la sous-requête à une seule ligne renvoie plus d'une ligne.

voici la solution:

BEGIN
For i in (select id, name, desc from table1) 
LOOP
Update table2 set name = i.name, desc = i.desc where id = i.id;
END LOOP;
END;

C'est exactement comme cela que vous l'exécutez sur la feuille de calcul SQLDeveloper. Ils disent que c'est lent mais c'est la seule solution qui a fonctionné pour moi dans ce cas.

Pau Karr
la source
quelqu'un peut-il expliquer pourquoi cela mérite un -2 sur la réputation? LOL.
Pau Karr
13
Je n'ai pas baissé le taux, mais ce n'est pas une bonne solution. Premièrement: si la sous-sélection renvoyait plusieurs valeurs, alors la boucle for remplacera le nom sur table2 plusieurs fois pour certains / tous les enregistrements (pas propre). Deuxièmement: il n'y a pas de clause order by, cela se produira de manière imprévisible (c'est-à-dire que la dernière valeur dans les données non ordonnées gagne). Troisièmement: ce sera beaucoup plus lent. En supposant que le résultat de la boucle for était prévu, la sous-sélection d'origine aurait pu être réécrite de manière contrôlée pour ne renvoyer qu'une seule valeur pour chaque enregistrement ... la manière la plus simple serait (sélectionnez min (nom) ...)
Alternator
C'était exactement ce dont j'avais besoin. Merci (+1)
Robert Hyatt
3
Si vous obtenez plusieurs valeurs dans votre sous-requête, vous pouvez repenser la requête et utiliser DISTINCT ou GROUP BY avec MIN, MAX. Juste une idée.
Francis
Pour faire court: si vous pouvez l'éviter, n'utilisez jamais JAMAIS de LOOP dans une instruction T-SQL. Personnellement, si ce n'était pas pour 0,001% du temps où il n'y a pas d'autre solution, je ne pense même pas que cela devrait même être une fonction disponible dans T-SQL. T-SQL est conçu pour être basé sur des ensembles, il fonctionne donc sur des ensembles entiers de données dans leur ensemble; il ne doit PAS être utilisé pour travailler sur des données ligne par ligne.
Ray K.
8

Voici une réponse encore meilleure avec la clause 'in' qui autorise plusieurs clés pour la jointure :

update fp_active set STATE='E', 
   LAST_DATE_MAJ = sysdate where (client,code) in (select (client,code) from fp_detail
  where valid = 1) ...

Le bœuf est d'avoir les colonnes que vous souhaitez utiliser comme clé entre parenthèses dans la clause where avant "in" et d'avoir l'instruction select avec les mêmes noms de colonne entre parenthèses. où ( colonne1, colonne2 ) dans ( sélectionnez ( colonne1, colonne2 ) dans le tableau où "l'ensemble que je veux" );

fourmi
la source
Le lien a expiré. ( 404)
Dumbo
-3

Si votre table t1 et sa sauvegarde t2 ont plusieurs colonnes, voici une façon compacte de le faire.

En outre, mon problème connexe était que seules certaines des colonnes ont été modifiées et que de nombreuses lignes n'ont pas été modifiées dans ces colonnes, je voulais donc les laisser tranquilles - restaurer essentiellement un sous-ensemble de colonnes à partir d'une sauvegarde de la table entière. Si vous souhaitez simplement restaurer toutes les lignes, ignorez la clause where.

Bien sûr, la manière la plus simple serait de supprimer et d'insérer comme sélectionner, mais dans mon cas, j'avais besoin d'une solution avec juste des mises à jour.

L'astuce est que lorsque vous sélectionnez * à partir d'une paire de tables avec des noms de colonne en double, la 2ème sera nommée _1. Voici donc ce que j'ai trouvé:

  update (
    select * from t1 join t2 on t2.id = t1.id
    where id in (
      select id from (
        select id, col1, col2, ... from t2
        minus select id, col1, col2, ... from t1
      )
    )
  ) set col1=col1_1, col2=col2_1, ...
Jim P
la source
Cela ne fonctionne pas pour moi dans Oracle 11g. Pouvez-vous créer un exemple de travail de cette méthode?
Jon Heller
-3
BEGIN
For i in (select id, name, desc from table2) 
LOOP
Update table1 set name = i.name, desc = i.desc where id = i.id and (name is null or desc is null);
END LOOP;
END;
Avila Theresa
la source