为啥 T-SQL CROSS APPLY 有时表现得像 LEFT JOIN

Posted

技术标签:

【中文标题】为啥 T-SQL CROSS APPLY 有时表现得像 LEFT JOIN【英文标题】:Why does a T-SQL CROSS APPLY sometimes behave like a LEFT JOIN为什么 T-SQL CROSS APPLY 有时表现得像 LEFT JOIN 【发布时间】:2021-06-14 14:55:20 【问题描述】:

我读过的大部分文档都表明,CROSS APPLY 的行为方式与 INNER JOIN 类似,只有在两个源表中都有匹配的行时,才会在输出中包含一行。

但是,情况似乎并非总是如此,例如,如果您运行以下 SQL 查询,结果将包含 3 行,其中一个包含许多 NULL,因为右侧没有行 -手桌:

CREATE TABLE #Order
(
    Id          int PRIMARY KEY
)

CREATE TABLE #OrderItem
(
    OrderId     int NOT NULL,
    Price       decimal(18, 2) NOT NULL
)

INSERT INTO #Order
VALUES(1), (2), (3)

INSERT INTO #OrderItem
VALUES(1, 10), (1, 20), (3,100)

SELECT *
FROM #Order o
CROSS APPLY
(
    SELECT SUM(Price) AS TotalPrice, COUNT(*) AS Items, MIN(Price) AS MinPrice
    FROM #OrderItem
    WHERE OrderId = o.Id
) t

DROP TABLE #Order
DROP TABLE #OrderItem

有人知道这是为什么吗?

【问题讨论】:

CROSS APPLY 永远不会像OUTER JOIN 那样行事。如果您愿意,您需要使用OUTER APPLY。您的子查询将始终返回一行,因为它只包含聚合,因此它始终返回一行。 @Larnu 但这正是 op 的查询中没有发生的事情。它返回 3 行,这就是问题 3 行是正确的。 1 表中的每一行 #Order. 我知道这是正确的,但就问题而言,它的行为有点像OUTER JOIN 【参考方案1】:

TL;DR;

发生这种情况的原因是聚合是一个标量聚合。


有两种类型的聚合:

向量聚合

需要一个GROUP BY 子句

如果输入没有行,则根本不返回任何行

标量聚合

没有GROUP BY 子句

始终返回至少一行,即使没有输入行。 COUNT 返回0,其他返回NULL

你使用的是一个标量聚合,所以总是返回一行。

要获得矢量聚合,您需要添加GROUP BY

SELECT *
FROM #Order o
CROSS APPLY
(
    SELECT SUM(oi.Price) AS TotalPrice, COUNT(*) AS Items, MIN(oi.Price) AS MinPrice
    FROM #OrderItem oi
    WHERE oi.OrderId = o.Id   -- always specify inner table in column references
    GROUP BY ()   -- the empty set
-- alternatively
    GROUP BY oi.OrderId
) t

另请参阅@PaulWhite 的这篇精彩文章: Fun with Scalar and Vector Aggregates

【讨论】:

谢谢 Charlieface,这解释了它,我注意到添加 GROUP BY 将结果集减少到 2 行 :-)【参考方案2】:

有人知道这是为什么吗?

因为您正在应用的查询会返回一行,无论是否有任何匹配的行,因为它是一个聚合查询。

【讨论】:

【参考方案3】:

您似乎认为,当没有适用的行时,聚合不会返回任何行。如果没有 GROUP BY 子句,则不是这样。采取以下无意义的查询:

SELECT COUNT(*) AS C,
       SUM(object_ID) AS S,
       MAX(object_ID) AS M
FROM sys.tables
WHERE [name]= N'sdfhjklsdgfgjklb807ty3480A645*)&TY0';

现在,除非您的某个对象有一个非常愚蠢的名称,否则您仍然会在这里得到一个只有一个的结果集:

C           S           M
----------- ----------- -----------
0           NULL        NULL

因此,对于您的查询,您的子查询中的每一行也有一行,因为它只包含聚合而没有GROUP BY

如果您不想要 Id 2 的行,那么您可以使用横向子查询或 WHERE

SELECT *
FROM #Order O
     JOIN (SELECT sq.OrderId,
                  SUM(sq.Price) AS TotalPrice,
                  COUNT(*) AS Items,
                  MIN(sq.Price) AS MinPrice
           FROM #OrderItem sq
           GROUP BY sq.OrderID) OI ON O.Id = OI.OrderID;

SELECT *
FROM #Order o
     CROSS APPLY(SELECT SUM(ca.Price) AS TotalPrice,
                        COUNT(*) AS Items,
                        MIN(ca.Price) AS MinPrice
                 FROM #OrderItem ca
                 WHERE ca.OrderId = o.Id) OI
WHERE OI.Items > 0;

【讨论】:

取决于你是否有GROUP BY sql.kiwi/2012/03/fun-with-aggregates.html

以上是关于为啥 T-SQL CROSS APPLY 有时表现得像 LEFT JOIN的主要内容,如果未能解决你的问题,请参考以下文章

为啥 CROSS APPLY 与列和聚合函数需要 Group by

使用 CROSS APPLY 与 OUTER APPLY 连接查询

使用 CROSS APPLY 与 OUTER APPLY 连接查询

使用 CROSS APPLY 与 OUTER APPLY 连接查询

SQLSERVER CROSS APPLY 与 OUTER APPLY 的应用

SQL Server - where + TVF/SVF、交叉应用、T-SQL