【问题标题】:Replacing subqueries with JOIN or WITH用 JOIN 或 WITH 替换子查询
【发布时间】:2016-02-11 18:43:06
【问题描述】:

this article,作者决定使用WITH替换这种方式使用的子查询:

SELECT c.CategoryName, p.ProductName, p.UnitPrice
    FROM Categories c
    INNER JOIN (SELECT CategoryId, MAX(UnitPrice) AS MaxPrice
        FROM Products GROUP BY CategoryId) maxprice
        ON maxprice.CategoryId = c.CategoryId
    INNER JOIN Products p
        ON p.CategoryId = c.CategoryId
            AND p.UnitPrice = maxprice.MaxPrice
    ORDER BY MaxPrice DESC

这是作者的查询结果:

WITH MostExpensiveProducts (CategoryId, MaxUnitPrice) AS
(
    SELECT CategoryId, MAX(UnitPrice)
        FROM Products
        GROUP BY CategoryId
)
SELECT c.CategoryName, p.ProductName, p.UnitPrice
    FROM Categories c
    INNER JOIN MostExpensiveProducts mep
        ON mep.CategoryId = c.CategoryId
    INNER JOIN Products p
        ON p.CategoryId = mep.CategoryId
            AND p.UnitPrice = mep.MaxUnitPrice
    ORDER BY mep.MaxUnitPrice DESC;

现在这篇文章已经超过 6 年了,但它仍然与我相关,因为我使用的是 SQL Server 2008。但是,我围绕替换子查询的大部分搜索结果都涉及JOINS。这让我很困惑,因为这里的子查询已经在 INNER JOINs 中。

这个使用WITH 的解决方案今天仍然有效吗?有没有更好的方法来解决这个问题?

【问题讨论】:

  • 您并没有真正删除子查询,只是使用 CTE,这对很多人来说提高了可读性
  • @Lamak 这就是我的想法。现在是否有替换此类子查询的实际解决方案?
  • 您认为实际的解决方案是什么?它和这个有什么不同?我不清楚你的目标是什么。
  • @TabAlleman 对不够清晰感到抱歉。关于涉及WITH 的解决方案,Lamak 表示它并没有真正删除子查询.. 那么是否有一个解决方案可以删除不涉及WITH 的子查询?

标签: sql-server join sql-server-2008-r2 subquery


【解决方案1】:

序言:

  • 加入本身并不是坏事
  • 在此示例中使用 WITH 纯粹是为了便于阅读(请参阅 WITH-ClauseLiterate SQL)。但是,我个人认为这个查询太少,无法应用这种技术——但这只是一个演示示例。

话虽如此,我觉得这个例子很连贯:它列出了每个类别中最昂贵的产品(如果一个类别中价格相同的产品越多,它可能会超过一个)。

话虽如此,仍然有一个不再需要的连接:Products 表的连接到WITH 子句的结果(不是p.UnitPrice = mep.MaxUnitPrice 上的连接。当我看到这个时,通常是一个错误。我现在假设。

如果打算展示每个类别中最昂贵的产品(在平局的情况下只有一个),那么@Tab Alleman 在我写作时发布的解决方案是可以的。如果您确实需要在出现平局的情况下列出多行,您可以使用RANK()(或@Tab Alleman 的解决方案中的DENSE_RANK() 而不是ROW_NUMBER()

使用更现代的 SQL 来解决此要求的另一种方法是使用 LATERAL:这允许您应用每个类别的 TOP(或 LIMITFETCH FIRST ... ROWS ONLY 子句)。在 SQL Server 中,这实际上称为 CROSS APPLYLATERAL 是 SQL 标准和其他数据库使用的关键字)。

SELECT c.CategoryName, p.ProductName, p.UnitPrice
  FROM Categories c
 CROSS APPLY (SELECT TOP 1 ProductName, UnitPrice
                FROM Products
               WHERE Products.Category = c.CategoryId
               ORDER BY UnitPrice DESC) p

(我没有运行此查询,错误是无意的;)如果出现平局,则未指定出现两个同等价格产品中的哪一个。

OVER 解决方案和LATERAL/APPLY 解决方案的性能可能会有所不同(最好取决于您拥有的数据和索引)。

LATERAL/APPLY 解决方案还可以在出现平局的情况下交付所有产品。 SQL 标准允许FETCH FIRST ... ROWS ONLY 具有WITH TIES 修饰符。显然,SQL Server 也有 TOP 1 WITH TIES (source)。

如果您想了解更多有关您可能还不知道的有用 SQL 功能的信息,请查看以下幻灯片:https://modern-sql.com/slides

【讨论】:

    【解决方案2】:

    此解决方案确实使用 CTE,但它使用 ROW_NUMBER() 而不是子查询来获取每个类别中最昂贵的产品,因此避免了两次加入产品:

    WITH cte AS (
      SELECT CategoryId, ProductId, UnitPrice,
        ROW_NUMBER() OVER (PARTITION BY CategoryId ORDER BY UnitPrice DESC) rn
      FROM Products
    )
    SELECT CategoryId, ProductId, UnitPrice
    FROM cte
    WHERE rn=1
    ORDER BY UnitPrice DESC
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-07
      • 1970-01-01
      • 2017-11-12
      • 1970-01-01
      • 1970-01-01
      • 2011-05-30
      • 2013-02-11
      相关资源
      最近更新 更多