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

9

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'
Wjdavis5
la source

Réponses:

21

Cette déclaration est légale (en d'autres termes, aucune FROMn'est requise):

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 WHEREclause 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);

De toute évidence, cela devrait être une erreur, non, car il n'y a pas d' fooentrée #bar? Nan. Ce qui se passe, c'est que SQL Server dit: "oh, je n'ai pas trouvé un fooici, vous devez avoir voulu dire l'autre."

Aussi, en général, j'éviterais NOT IN. NOT EXISTSa 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 puisse l'être NULL. Voir cet article pour plus d'informations .

Aaron Bertrand
la source
J'ai posé une question sur Stack Overflow à laquelle la réponse est essentiellement la même que celle-ci (bien que la vôtre soit plus approfondie). Pourquoi le référencement d'une colonne (en tant qu'opérande de gauche) qui ne fait pas partie de la table interrogée n'est-il pas une erreur dans l'opérateur EXISTS?
Marc.2377
2

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.

paulbarbin
la source
1

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.

Piotr
la source
Mais cela ne fonctionne pas si vous dites simplement select cet cn'existe pas dans un objet extérieur. Je suis d' accord que FROMn'est pas nécessaire, mais la mécanique en jeu ici lorsque vous nommez explicitement une colonne qui fait exister dans un champ extérieur sont certainement différent de celui d' une table factice, et si vous ne fournissez pas une constante pour une colonne qui ne existe, vous obtenez une erreur d'exécution, donc pas de table factice là non plus. La table factice peut entrer en jeu dans d'autres scénarios, mais pas lorsque la référence se trouve dans une table de sous-requête / dérivée.
Aaron Bertrand
Dans votre exemple, il s'agit d'une sous-sélection corrélée, role_id et role_object_id appartient à l'une des tables de la sélection externe.
Piotr
C'est vrai, mais dire SELECT 'x' AS cest un scénario complètement différent de celui des PO, qui vient de le dire SELECT c. Dans une sous-requête / table dérivée.
Aaron Bertrand