Dangerous Update
Oracle

A volte anche dietro ad un SQL statement  apparentemente “banale” possono nascondersi delle insidie:

UPDATE y
 SET c2 = (SELECT c2
          FROM z
          WHERE z.c1 = y.c1);

L’errore più frequente che ho riscontrato è quello di pensare che l’UPDATE sopra riportata avvenga solo su  corrispondenti valori di C1 tra Y e Z, ma non è così, un esempio chiarirà meglio il concetto. Si supponga di avere due tabelle Y e Z con i seguenti valori:
 
SQL> select * from y;
        C1 C2
---------- --
         1 y
         2 y
         3 y
         4 y
         5 y
         6 y
         7 y
         8 y
         9 y
        10 y

Selezionate 10 righe.

SQL> select * from z;
        C1 C2
---------- --
         3 z
         4 z
         5 z
         6 z
         7 z
         8 z
         9 z
        10 z
        11 z
        12 z

Selezionate 10 righe.

Quale sarà l’effetto dello statement SQL di UPDATE? L’effetto atteso dagli sviluppatori PL/SQL che utilizzavano questa sintassi doveva essere quello di aggiornare i valori della colonna C2 della tabella Y con i valori della colonna C2 della tabella Z solo in presenza di match (JOIN) tra le colonne C1 (C1 = 3..10). Ma era sfuggito un piccolo(grande) dettaglio, lo statement così strutturato soffriva infatti di un problema: 
SQL> UPDATE y
  2 SET c2 = (SELECT c2
  3           FROM z
  4           WHERE z.c1 = y.c1);
Aggiornate 10 righe.
SQL> SELECT * FROM y;

        C1 C2
---------- --
         1
         2
         3 z
         4 z
         5 z
         6 z
         7 z
         8 z
         9 z
        10 z

Il risultato ottenuto infatti è stato quello di sovrascrivere tutti i valori della colonna C2 della tabella Y causando quindi anche l’aggiornamento con valore null dei valori di C2 che non dovevano invece essere modificati.
SQL> rollback;
Rollback completato.
SQL> SELECT * FROM y;
        C1 C2
---------- --
         1 y
         2 y
         3 y
         4 y
         5 y
         6 y
         7 y
         8 y
         9 y
        10 y

Selezionate 10 righe.
Dopo aver ripristinato i valori originali mediante comando di ROLLBACK vediamo le possibili soluzioni:

Prima soluzione (Update con clausula EXISTS)

Il risultato voluto si ottiene aggiungendo una EXISTS che limita il risultato alle sole righe da aggiornare rispettando così il vincolo imposto z.c1=y.c1:

SQL> UPDATE y
  2  SET c2 = (SELECT c2
  3            FROM z;
  4            WHERE z.c1 = y.c1)
  5  WHERE EXISTS  (SELECT 1
  6                 FROM z
  7                 WHERE z.c1 = y.c1);

Aggiornate 8 righe.

SQL> select * from y;

        C1 C2
---------- --
         1 y
         2 y
         3 z
         4 z
         5 z
         6 z
         7 z
         8 z
         9 z
        10 z

Selezionate 10 righe.

Seconda soluzione (Utilizzo delle PK)

Per utilizzare questa soluzione occorre apportare una piccola modifica alla sintassi dello statement SQL e creare una PK definita sulla colonna C1 su entrambe le tabelle, in caso contrario verrà sollevato il seguente errore:
SQL> update (select y.c2, z.c2 z_c2
  2          from y,z
  3          where z.c1 = y.c1)
  4   set c2 = z_c2;
  set c2 = z_c2
*
ERRORE alla riga 4:
ORA-01779: impossibile modificare una colonna riferita ad una tabella senza mantenimento delle chiavi
Creo le PK e rieseguo l'UPDATE
SQL> ALTER TABLE y ADD PRIMARY KEY (c1);

Tabella modificata.

SQL> ALTER TABLE z ADD PRIMARY KEY (c1);

Tabella modificata.

SQL> UPDATE (SELECT y.c2, z.c2 z_c2
  2          FROM y,z
  3          WHERE z.c1 = y.c1)
  4   SET c2 = z_c2;

Aggiornate 8 righe.
SQL> SELECT * FROM y;
        C1 C
---------- -
         1 y
         2 y
         3 z
         4 z
         5 z
         6 z
         7 z
         8 z
         9 z
        10 z

Selezionate 10 righe.

Come abbiamo visto anche in questo caso l'UPDATE funziona correttamente.

Terza soluzione (Utilizzo dell'istruzione MERGE)

La terza soluzione prevede l’utilizzo di uno statement di MERGE, questa soluzione funziona sia in presenza che in assenza di PK:
SQL> ALTER TABLE y DROP PRIMARY KEY;

Tabella modificata.

SQL> ALTER TABLE z DROP PRIMARY KEY;

Tabella modificata.

SQL> MERGE INTO y
  2    USING (SELECT c1, c2
  3           FROM z) z
  4    ON (y.c1 = z.c1)
  5    WHEN MATCHED THEN
  6         UPDATE SET y.c2 = z.c2;

8 di righe unite.

SQL> SELECT * FROM Y;
        C1 C
---------- -
         1 y
         2 y
         3 z
         4 z
         5 z
         6 z
         7 z
         8 z
         9 z
        10 z

condividi su
Donatello Settembrino
AUTORE Donatello Settembrino

Oracle Specialist at Performance Team @ ICTeam Spa