web-dev-qa-db-fra.com

Pourquoi cette requête, manquant une clause FROM, n'est-elle pas une erreur?

Nous avons donc une requête avec une sous-requête qui contient une faute de frappe. Il manque la clause FROM. Mais lorsque vous l'exécutez, cela ne produit pas d'erreur! Pourquoi!?


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'
9
Wjdavis5

Cette déclaration est légale (en d'autres termes, aucun FROM n'est requis):

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

L'astuce consiste à introduire un nom de colonne qui ne peut clairement pas exister. Donc ceux-ci échouent:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Msg 207, niveau 16, état 1
Nom de colonne non valide 'nom'.
Msg 207, niveau 16, état 1
Nom de colonne non valide 'id'.

Mais lorsque la colonne non valide est introduite dans quelque chose comme une sous-requête, ce que fait SQL Server lorsqu'il ne peut pas trouver cette colonne dans la portée interne de la sous-requête, est traversé vers une portée externe et rend la sous-requête corrélée à cette portée externe. Cela renverra toutes les lignes, par exemple:

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

Parce qu'il dit essentiellement:

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

Vous n'avez même pas besoin d'une clause WHERE dans la sous-requête:

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

Vous pouvez voir qu'il regarde vraiment la table de portée externe, car ceci:

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

Renvoie beaucoup moins de lignes (11 sur mon système).

Cela implique le respect de la norme sur la portée. Vous pouvez voir des choses similaires lorsque vous avez deux tables #temp:

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

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

Évidemment, cela devrait être une erreur, à droite, car il n'y a pas de foo dans #bar? Nan. Ce qui se passe, c'est que SQL Server dit: "oh, je n'ai pas trouvé de foo ici, vous devez avoir voulu dire l'autre."

De plus, en général, j'éviterais NOT IN. NOT EXISTS a le potentiel d'être plus efficace dans certains scénarios, mais plus important encore, son comportement ne change pas lorsqu'il est possible que la colonne cible soit NULL. Voir cet article pour plus d'informations .

21
Aaron Bertrand

Je l'ai reproduit en 2016 avec un exemple simplifié:

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)

Il apparaît que c2 et c3 sont évalués pour chaque ligne.

2
paulbarbin

Dans SQL Server SELECT, la syntaxe ne nécessite pas de section FROM. Si vous omettez FROM, l'instruction select utilisera la table "factice" qui a une ligne et aucune colonne. Donc

select 'x' as c where ...

renverra une ligne si l'expression est vraie et aucune ligne lorsqu'elle est fausse.

1
Piotr