Perché questa query, mancante di una clausola FROM, non genera errori?


9

Quindi abbiamo una query con una sottoquery che contiene un refuso. Manca la clausola FROM. Ma quando lo esegui, non si guasta! Perché!?


SELECT

    1
   ,r.id
   ,'0D4133BE-C1B5-4141-AFAD-B171A2CCCE56'
   ,GETDATE()
   ,1
   ,'Y'
   ,'N'
   ,oldItem.can_view
   ,oldItem.can_update

FROM Role r

JOIN RoleObject oldReport
    ON r.customer_id = oldReport.customer_id

JOIN RoleItem oldItem
    ON oldReport.id = oldItem.role_object_id
        AND r.id = oldItem.role_id

WHERE r.id NOT IN (SELECT
        role_id
    WHERE role_object_id = '0D4133BE-C1B5-4141-AFAD-B171A2CCCE56')

AND oldReport.id = '169BA22F-1614-4EBA-AF45-18E333C54C6C'

Risposte:


21

Questa affermazione è legale (in altre parole, non FROMè richiesto):

SELECT x = 1;
SELECT x = 1 WHERE 1 = 1; -- also try WHERE 1 = 0;

Il trucco è quando si introduce un nome di colonna che chiaramente non può esistere. Quindi questi falliscono:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Messaggio 207, livello 16, stato 1
Nome colonna 'nome' non valido.
Messaggio 207, livello 16, stato 1
Nome colonna "id" non valido.

Ma quando la colonna non valida viene introdotta in qualcosa di simile a una sottoquery, cosa fa SQL Server quando non riesce a trovare quella colonna nell'ambito interno della sottoquery, si attraversa un ambito esterno e rende la sottoquery correlata a tale ambito esterno. Ciò restituirà tutte le righe, ad esempio:

SELECT * FROM sys.columns WHERE name IN (SELECT name WHERE 1 = 1);

Perché essenzialmente sta dicendo:

SELECT * FROM sys.columns WHERE name IN (SELECT sys.columns.name WHERE 1 = 1); /*
              ^^^^^^^^^^^                       -----------
                   |                                 |
                   -----------------------------------    */

Non è nemmeno necessaria una WHEREclausola nella sottoquery:

SELECT * FROM sys.columns WHERE name IN (SELECT name);

Puoi vedere che sta davvero guardando la tabella con ambito esterno, perché questo:

SELECT * FROM sys.columns WHERE name IN (SELECT name WHERE name > N'x');

Restituisce molte meno righe (11 sul mio sistema).

Ciò comporta l'adesione allo standard sull'ambito. Puoi vedere cose simili quando hai due tabelle #temp:

CREATE TABLE #foo(foo int);
CREATE TABLE #bar(bar int);

SELECT foo FROM #foo WHERE foo IN (SELECT foo FROM #bar);

Ovviamente, questo dovrebbe errore, a destra, dal momento che non v'è fooin #bar? No. Quello che succede è che SQL Server dice "oh, non ho trovato un fooqui, devi aver inteso l'altro".

Inoltre, in generale, eviterei NOT IN. NOT EXISTSha il potenziale per essere più efficiente in alcuni scenari, ma ancora più importante, il suo comportamento non cambia quando è possibile che la colonna target sia NULL. Vedi questo post per maggiori informazioni .


Ho fatto una domanda su Stack Overflow a cui la risposta è essenzialmente la stessa di questa (sebbene la tua sia più approfondita). Perché fare riferimento a una colonna (come operando di sinistra) che non fa parte della tabella interrogata non è un errore nell'operatore EXISTS?
Marc.2377,

2

L'ho riprodotto nel 2016 con un esempio semplificato:

declare @t1 table (c1 int, c2 int, c3 int)
insert into @t1 values (1,2,3), (2,3,4), (3,4,5)

select * from @t1
where
    c1 not in 
    (select c2 where c3 = 3)

Sembra che c2 e c3 vengano valutati per ogni riga.


1

In SQL Server la sintassi SELECT non richiede la sezione FROM. Se si omette FROM, l'istruzione select utilizzerà la tabella "fittizia" che ha una riga e nessuna colonna. Così

select 'x' as c where ...

restituirà una riga se l'espressione è vera e nessuna riga quando è falsa.


Ma questo non funziona se lo dici select ce cnon esiste in qualche oggetto esterno. Sono d'accordo che FROMnon è necessario, ma i meccanici in gioco qui quando è il nome in modo esplicito una colonna che fa esistere in un ambito esterno sono sicuramente diverso rispetto a un tavolo fittizio, e se non si fornisce una costante per una colonna che non lo fa esiste, si ottiene un errore di runtime, quindi nemmeno una tabella fittizia lì. La tabella fittizia può entrare in gioco in altri scenari, ma non quando il riferimento si trova in una tabella secondaria / derivata.
Aaron Bertrand

Nel tuo esempio è una sottoselezione correlata, role_id e role_object_id appartiene a una delle tabelle nella selezione esterna.
Piotr

Giusto, ma dire SELECT 'x' AS cè uno scenario completamente diverso rispetto ai PO, che hanno appena detto SELECT c. In una tabella secondaria / derivata.
Aaron Bertrand
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.