为啥 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 连接查询