Oggi mi è stato sottoposto un problema veramente curioso. Una collega mi ha fatto notare che durante un’operazione di manutenzione di un DB SQL Server 2008 R2 ha lanciato una query di DELETE che non si è comportata come previsto. In pratica, questa query stava cancellando arbitrariamente i dati dalla tabella senza rispettare la clausola WHERE che compariva regolarmente nel testo della query.
Il comando dato al server è il seguente:
DELETE FROM dbo.Utenti WHERE IDUtente IN (SELECT IDUtente FROM dbo.Operatori)
Come mai il comportamento è strano? Perchè effettivamente in questa query c’è un errore! La tabella Operatori NON contiene il campo IDUtente, per cui, lanciato da solo, il comando
SELECT IDUtente FROM dbo.Operatori
Dà un errore di tipo:
Msg 207, Level 16, State 1, Line 1 Invalid column name 'IDUtente'.
Lo stesso errore non viene riportato se la query viene lanciata come subquery della clausola IN, bensì la query viene eseguita completamente, cancellando TUTTI i record dalla tabella Utenti, ignorando per cui la clausola WHERE IDUtente IN (…).
E’ da notare come utilizzando un nome di campo non esistente nè nella tabella Utenti, nè nella tabella Operatori la query vada in errore; per cui è da supporre che il comando non vada in errore proprio perchè il campo IDUtente compare nella tabella Utenti (cosa comunque errata).
Per ricreare il “problema” ed indagare ulteriormente, ho creato un piccolo DB con 2 tabelle, Utenti e Operatori, così strutturate:
- Utenti
- IDUtente [int] PK
- Nome [varchar(50)]
- Operatori
- IDOperatore [int] PK
- Nome [varchar(50)]
nella tabella Utenti ho 5 records (con IDUtente 1,2,3,4,5), mentre in quella Operatori ne ho 2 (con IDOperatore 1,2)
Questa query seleziona tutti gli utenti:
SELECT * FROM dbo.Utenti
Questa query seleziona solo gli utenti 1 e 2
SELECT * FROM dbo.Utenti WHERE IDUtente IN (SELECT IDOperatore FROM dbo.Operatori)
Questa query restituisce un errore:
SELECT IDUtente FROM dbo.Operatori
Msg 207, Level 16, State 1, Line 1 Invalid column name 'IDUtente'.
Questa query, stranamente, restituisce TUTTI gli utenti:
SELECT * FROM dbo.Utenti WHERE IDUtente IN (SELECT IDUtente FROM dbo.Operatori)
Non dovrebbe restituire tutti gli utenti, in quanto il campo IDUtente non fa parte della tabella Operatori. Altrettanto stranamente anche questa query restituisce tutti gli utenti:
SELECT * FROM dbo.Utenti WHERE IDUtente IN (SELECT IDUtente)
Per induzione, penso che SQL interpreti il campo IDUtente come campo dell’unica tabella elencata nella clausola FROM, anche se questo è a mio avviso fuorviante.
Quindi, quando fate una query di delete con una clausola IN, fate particolare attenzione ai nomi dei campi, e magari prima di eseguirla provate a fare una SELECT.