web-dev-qa-db-fra.com

Utilisation d'une sous-requête au lieu d'un nom de table dans une instruction de mise à jour Oracle

J'ai besoin d'écrire une instruction de mise à jour utilisant plusieurs tables pour déterminer les lignes à mettre à jour, car dans Oracle, plusieurs tables ne sont pas autorisées. La requête suivante renvoie l'erreur "ORA-00971: Mot clé SET manquant"

UPDATE
  TABLE1 a,
  TABLE2 b
SET
  a.COL1 = 'VALUE'
WHERE
  a.FK = b.PK
  AND b.COL2 IN ('SET OF VALUES')

En recherchant la syntaxe d'instruction UPDATE sous Oracle, j'ai trouvé le code link suivant, qui indique que vous pouvez utiliser une sous-requête à la place d'un nom de table.

Lorsque j'ai essayé d'écrire la requête de cette manière, j'ai reçu le message "ORA-01779: Impossible de modifier une colonne mappée sur une table non protégée par clé".

UPDATE
  (
    SELECT
      a.COL1
    FROM
      TABLE1 a,
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
  ) update_tbl
SET
  update_tbl.COL1 = 'VALUE'

J'ai réécrit la requête (voir ci-dessous) en utilisant une instruction EXISTS à la place et cela fonctionne bien, mais je voudrais tout de même savoir comment cela est fait.

UPDATE
  TABLE1 update_tbl
SET
  update_tbl.COL1 = 'VALUE'
WHERE
  EXISTS (
    SELECT
      1
    FROM
      TABLE1 a
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
      AND update_tbl.PK = a.PK
  )

Merci! - Nate

13
NateSchneider

Une autre option:

UPDATE TABLE1 a
SET a.COL1 = 'VALUE'
WHERE a.FK IN
( SELECT b.PK FROM TABLE2 b
  WHERE b.COL2 IN ('SET OF VALUES')
)

Votre deuxième exemple fonctionnerait si (a) la vue incluait le déclaré PK de TABLE1:

UPDATE
  (
    SELECT
      a.COL1, a.PKCOL
    FROM
      TABLE1 a,
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
  ) update_tbl
SET
  update_tbl.COL1 = 'VALUE'

... et (b) TABLE1.FK était une clé déclarée étrangère à TABLE2

(Par déclaré, je veux dire qu'une contrainte existe et est activée).

7
Tony Andrews

Je trouve qu'un moyen agréable, rapide et cohérent de transformer une instruction SELECT en une mise à jour consiste à effectuer la mise à jour en fonction du ROWID.

UPDATE
  TABLE1
SET
  COL1 = 'VALUE'
WHERE
  ROWID in
    (
    SELECT
      a.rowid
    FROM
      TABLE1 a,
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
    )

Votre requête interne définit donc les lignes à mettre à jour.

6
Nick Pierpoint

La syntaxe de votre exemple est correcte, mais Oracle requiert que la sous-requête inclue des clés primaires. C'est une limitation assez importante.

Sur une note connexe, vous pouvez également utiliser des parenthèses pour utiliser 2 ou plusieurs champs dans une instruction IN, comme dans:

UPDATE
  TABLE1 update_tbl
SET
  update_tbl.COL1 = 'VALUE'
WHERE
  (update_tbl.PK1, update_tbl.pk2) in(
                      select some_field1, some_field2
                      from some_table st
                      where st.some_fields = 'some conditions'
                      );
3
JosephStyons

Lorsque vous effectuez une mise à jour, vous pouvez évidemment uniquement demander au système de mettre à jour la valeur avec une seule nouvelle valeur. Lui dire de mettre à jour "X" en "Y" et "Z" n'a pas de sens. Ainsi, lorsque vous basez une mise à jour sur le résultat d'une vue en ligne, Oracle vérifie que les contraintes en place sont suffisantes pour empêcher une colonne modifiée d'être potentiellement mise à jour deux fois.

Dans votre cas, je suppose que TABLE2.PK n'est pas réellement une clé primaire déclarée. Si vous placez une contrainte primaire ou unique sur cette colonne, vous pourrez alors vous en aller.

Il existe un indice non documenté pour contourner la mise à jour, rejoindre la vérification de cardinalité, utilisée en interne par Oracle, mais je ne vous conseillerais pas de l'utiliser.

Une solution de contournement pour cela consiste à utiliser une instruction MERGE, qui n'est pas soumise au même test.

2
David Aldridge

J'ai trouvé ce dont j'avais besoin ici: Commandes SQL utiles

Je devais mettre à jour une table avec le résultat d'une jointure
J'ai essayé les solutions ci-dessus sans succès :(

Voici un extrait de la page que j'ai pointée plus haut
À l'aide de curseurs, j'ai pu mener à bien cette tâche.
Je suis sûr qu'il y a une autre solution mais celle-ci a fonctionné alors ...

DECLARE

 /* Output variables to hold the result of the query: */
 a T1.e%TYPE;
 b T2.f%TYPE;
 c T2.g%TYPE;

 /* Cursor declaration: */
 CURSOR T1Cursor IS
   SELECT T1.e, T2.f, T2.g
   FROM T1, T2
   WHERE T1.id = T2.id AND T1.e <> T2.f

 FOR UPDATE;

BEGIN

  OPEN T1Cursor;

  LOOP

    /* Retrieve each row of the result of the above query
    into PL/SQL variables: */
    FETCH T1Cursor INTO a, b;

    /* If there are no more rows to fetch, exit the loop: */
    EXIT WHEN T1Cursor%NOTFOUND;

    /* Delete the current Tuple: */
    DELETE FROM T1 WHERE CURRENT OF T1Cursor;

    /* Insert the reverse Tuple: */
    INSERT INTO T1 VALUES(b, a);

    /* Here is my stuff using the variables to update my table */
    UPDATE T2
    SET T2.f = a
    WHERE T2.id = c;

  END LOOP;

  /* Free cursor used by the query. */
  CLOSE T1Cursor;

END;
.
run;


Note: N'oubliez pas de vous engager ;-)

1
Franck

Chaque ligne du jeu de résultats de la requête de votre clause UPDATE doit être reliée à une et une seule ligne de la table que vous essayez de mettre à jour, et d'une manière que Oracle peut suivre automatiquement. Étant donné que la requête est réellement une vue, Oracle doit pouvoir rejoindre la vue dans la table cible afin de savoir quelle ligne mettre à jour.

Cela signifie essentiellement que vous devez inclure la clé primaire de la table de destination dans cette requête. Vous pourrez peut-être également utiliser d'autres champs d'index uniques, mais je ne peux pas garantir que le SGBD Oracle est suffisamment intelligent pour le permettre.

0
Chris Ammerman