SQL Server 2012中带聚合函数的CROSS APPLY无结果时是否返回行?
嘿,这个场景我太熟悉了!先给你明确结论:这不是bug,是SQL Server的预期行为——当CROSS APPLY的内部表达式没有返回任何行时,只要内部用了聚合函数,它依然会返回主表对应的行,只是聚合结果会是NULL(COUNT(*)除外,会返回0)。
举个实际例子对比
假设我们有主表Customers和子表Orders,看两种核心场景的差异:
情况1:不带聚合的CROSS APPLY
SELECT c.CustomerID, o.OrderID FROM Customers c CROSS APPLY ( SELECT OrderID FROM Orders o WHERE o.CustomerID = c.CustomerID ) o
如果某个客户没有任何订单,内部查询无结果,CROSS APPLY会像INNER JOIN一样过滤掉这个客户的行,最终结果里不会出现该客户。
情况2:带聚合函数的CROSS APPLY
SELECT c.CustomerID, o.TotalOrderAmount FROM Customers c CROSS APPLY ( SELECT SUM(OrderAmount) AS TotalOrderAmount FROM Orders o WHERE o.CustomerID = c.CustomerID ) o
这时候哪怕客户没有订单,内部的SUM()函数在无行输入时会返回NULL,而CROSS APPLY会把这个包含NULL的结果行和主表行做关联,所以该客户的行会保留在结果中,TotalOrderAmount为NULL。如果用的是COUNT(*),则会返回0,同样会保留主表行。
为什么会这样?
这是聚合函数的特性决定的:当没有匹配的行时,除了COUNT(*)返回0,其他聚合函数都会返回一个NULL值,并且这个聚合操作会生成一行结果(哪怕只有NULL)。而CROSS APPLY的逻辑是把主表的每一行和内部查询的结果行做关联,既然内部查询有一行结果(NULL行),那主表的行自然会被保留下来。
如果想过滤掉无结果的行怎么办?
如果你希望带聚合的CROSS APPLY也像不带聚合时一样过滤掉无匹配的主表行,可以在内部查询里加HAVING子句:
SELECT c.CustomerID, o.TotalOrderAmount FROM Customers c CROSS APPLY ( SELECT SUM(OrderAmount) AS TotalOrderAmount FROM Orders o WHERE o.CustomerID = c.CustomerID HAVING SUM(OrderAmount) IS NOT NULL -- 过滤聚合结果为NULL的情况 ) o
如果是用COUNT(*),则改成HAVING COUNT(*) > 0即可。
额外提示
其实这种特性有时候很实用——比如你需要给所有主表行都返回一个聚合统计值,哪怕没有子行匹配,用带聚合的CROSS APPLY比LEFT JOIN后再聚合要更简洁高效,刚好契合你平时用CROSS APPLY替代派生表的习惯~
内容的提问来源于stack exchange,提问作者cohena

