为什么此查询缺少FROM子句而不出错?


9

因此,我们有一个带有带有错字的子查询的查询。它缺少FROM子句。但是,当您运行它时,它不会出错!为什么!?


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'

Answers:


21

此声明是合法的(换句话说,FROM不需要):

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

诀窍是当您引入一个明显不存在的列名时。因此这些失败:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

消息207,级别16,状态1
无效的列名称“名称”。
消息207,级别16,状态1
无效的列名称'id'。

但是,当在子查询之类的内容中引入无效列时,SQL Server在子查询的内部作用域中找不到该列时,将遍历外部作用域,并使子查询与该外部作用域相关联。这将返回所有行,例如:

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

因为它实际上是在说:

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

您甚至不需要WHERE子查询中的子句:

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

您可以看到它实际上是在查看外部作用域表,因为:

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

返回少得多的行(在我的系统上为11)。

这涉及遵守有关范围界定的标准。当您有两个#temp表时,您会看到类似的事情:

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

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

显然,这应该出错,对,因为没有fooin #bar?不。发生的是,SQL Server说:“哦,我没有在foo这里找到,您一定是指另一个。”

另外,一般而言,我会避免使用NOT INNOT EXISTS在某些情况下具有提高效率的潜力,但更重要的是,当目标列可能为时,其行为不会改变NULL有关更多信息,请参见此帖子


我问了一个关于Stack Overflow的问题,答案基本上与此相同(尽管您的回答更为详尽)。为什么引用不作为查询表一部分的列(作为左操作数)在EXISTS运算符中不会出错?
Marc.2377

2

我在2016年用一个简化的示例重现了这一点:

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)

似乎对每一行都评估了c2和c3。


1

在SQL Server SELECT语法中,不需要FROM部分。如果省略FROM,select语句将使用“虚拟”表,该表只有一行,没有列。所以

select 'x' as c where ...

如果表达式为true,则返回一行;如果为false,则不返回行。


但是,如果您只是说而已select c并且c在某些外部对象中不存在,那将是行不通的。我同意这FROM不是必需的,但是当您显式命名确实存在于外部作用域中的列时,此处的作用机制绝对不同于虚拟表,并且如果您不为不提供该作用的列提供常量存在,则会出现运行时错误,因此也没有虚拟表。虚拟表可以在其他情况下发挥作用,但是当引用在子查询/派生表中时则不能。
亚伦·伯特兰

在您的示例中,它是一个关联的子选择,role_id和role_object_id属于外部选择中的表之一。
Piotr

是的,但是说出来的SELECT 'x' AS c情况与OP 所说的完全不同SELECT c在子查询/派生表中。
亚伦·伯特兰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.