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'
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 .
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.
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.