【问题标题】: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

标签: sql-server cross-apply


【解决方案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;
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-04
    • 2019-10-09
    • 1970-01-01
    • 1970-01-01
    • 2014-02-07
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    相关资源
    最近更新 更多