问题描述
我读过的大多数文档都表明 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
有人知道这是为什么吗?
解决方法
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
,有人知道这是为什么吗?
因为无论是否有任何匹配的行,您正在申请的查询都会返回一行,因为它是一个聚合查询。
,您似乎认为当没有适用的行时,聚合不会返回任何行。如果没有 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,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,MIN(ca.Price) AS MinPrice
FROM #OrderItem ca
WHERE ca.OrderId = o.Id) OI
WHERE OI.Items > 0;