OUTER/CROSS 不带 FROM 子句的 APPLY 子查询

OUTER/CROSS APPLY Subquery without FROM clause

大多数讨论 OUTER|CROSS APPLY 的在线文档或教程描述如下内容:

SELECT columns
FROM table OUTER|CROSS APPLY (SELECT … FROM …);

子查询通常是完整的 SELECT … FROM … 查询。

我一定在某处读到子查询不需要 FROM,在这种情况下,列似乎来自主查询:

SELECT columns
FROM table OUTER|CROSS APPLY (SELECT … );

因为我经常使用它作为预计算列的方法。

问题是,如果从子查询中省略 FROM,到底发生了什么?它是别的东西的缩写吗?我发现它确实 not 与主要 table.

的意思相同

我这里有一个样本:http://sqlfiddle.com/#!18/0188f7/4/1

首先考虑

SELECT o.name, o.type
FROM sys.objects o

现在考虑

SELECT o.name, (SELECT o.type) AS type
FROM sys.objects o

没有 FROMSELECT 就像是从假想的单行 table 中选择一样。以上并没有改变标量子查询的结果,它只是作为一个相关的子查询,并使用来自外部查询的值。

APPLY 的行为方式相同。来自外部查询的对列的引用只是作为相关参数传入。所以这和

是一样的
SELECT o.name, ca.type
FROM sys.objects o
CROSS APPLY (SELECT o.type) AS ca

但是 APPLY 通常比 SELECT 中的标量子查询更有能力(因为它可以扩展行或从结果中删除行)

您提到的不是SUBQUERY。它是单独的 table 表达式。是否在正确的表达式中使用 FROM 子句是个问题。

  1. 如果您在右 table 表达式中使用 FROM 子句,那么您就获得了右 table 表达式中的数据源。
  2. 如果您不在右侧表达式中使用 FROM 子句,则您的数据源来自左侧 table 表达式。

首先我们将了解什么是 APPLY 运算符。 Reference BOL

Using APPLY

Both the left and right operands of the APPLY operator are table expressions. The main difference between these operands is that the right_table_source can use a table-valued function that takes a column from the left_table_source as one of the arguments of the function. The left_table_source can include table-valued functions, but it cannot contain arguments that are columns from the right_table_source.

The APPLY operator works in the following way to produce the table source for the FROM clause:

  1. Evaluates right_table_source against each row of the left_table_source to produce rowsets.

    The values in the right_table_source depend on left_table_source. right_table_source can be represented approximately this way: TVF(left_table_source.row), where TVF is a table-valued function.

  2. Combines the result sets that are produced for each row in the evaluation of right_table_source with the left_table_source by performing a UNION ALL operation.

    The list of columns produced by the result of the APPLY operator is the set of columns from the left_table_source that is combined with the list of columns from the right_table_source.

根据您使用 APPLY 运算符的方式,它将表现为相关子查询或 CROSS JOIN

  1. 在右侧table表达式
  2. 中使用左侧table表达式的值
--  without FROM (similar to Correlated Subquery)
    SELECT id, data, value
    FROM test OUTER APPLY(SELECT data*10 AS value) AS sq;
  1. 没有在右 table 表达式
  2. 中使用左 table 表达式的值
--  FROM table (Similar to cross join)
    SELECT id, data, value
    FROM test OUTER APPLY(SELECT data*10 AS value FROM test) AS sq;

省略 FROM 语句并不特定于 CROSS/OUTER APPLY;任何有效的 SQL select 语句都可以省略它。如果不使用 FROM,您就没有数据来源,因此您无法在该来源中指定列。相反,您只能 select 已经存在的值;是在语句本身中定义的常量,或者在某些情况下(例如子查询)从查询的其他部分引用的列。

如果您熟悉 Oracle 的 Dual table,这将更容易理解;一个 table 有 1 行。在 MS SQL 中,table 看起来像这样:

-- Ref: https://blog.sqlauthority.com/2010/07/20/sql-server-select-from-dual-dual-equivalent/
CREATE TABLE DUAL
(
    DUMMY VARCHAR(1) NOT NULL 
    , CONSTRAINT CHK_ColumnD_DocExc  CHECK (DUMMY = 'X') -- ensure this column can only hold the value X 
    , CONSTRAINT PK_DUAL PRIMARY KEY (DUMMY) -- ensure we can only have unique values... combined with the above means we can only ever have 1 row
)  
GO
INSERT INTO DUAL (DUMMY)
VALUES ('X')
GO

然后您可以select 1 one, 'something else' two from dual。你并没有真正使用双重;只是确保你有一个 table 总是 return 恰好 1 行。

现在在 SQL 任何地方你省略了一个 FROM 语句认为该语句好像它说 FROM DUAL / 它具有相同的含义,只有 SQL 允许更多shorthand方法。

更新

您在评论中提到您没有看到如何在子查询中引用原始语句中的列(例如,您在使用 APPLY 时可能会看到的那种)。下面的代码在没有 APPLY 场景的情况下显示了这一点。诚然,这里的演示代码不是您曾经使用过的东西(因为您可以在原始语句上 where Something like '%o%' 而不需要 subquery/in 语句),但出于说明目的,它显示了完全相同的场景就像您的 APPLY 场景一样;也就是说,该语句只是 returning 当前行的 SOMETHING 的值。

declare @someTable table (
    Id bigint not null identity(1,1)
    , Something nvarchar(32) not null
)
insert @someTable (Something) values ('one'), ('two'), ('three')

select * 
from @someTable x
where x.Something in 
(
    -- this subquery references the SOMETHING column from above, but doesn't have a FROM statement
    -- note: there is only 1 value at a time for something here; not all 3 values at once; it's the same single value as Something as we have before the in keyword above
    select Something 
    where Something like '%o%'
)