用JOIN或WITH替换子查询

时间:2016-02-11 18:43:06

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

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这个解决方案今天仍然有效吗?有没有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:2)

序言:

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

话虽如此,我发现这个例子非常有线:它列出了每个类别中最昂贵的产品(如果一个类别中的产品价格相同,可能会超过一个)。

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

如果打算每个类别显示最昂贵的产品(只有一个关系),@ Tab Alleman在我写作时发布的解决方案是可以的。如果您确实需要列出多行,如果有关系,您可以在@Tab Alleman的解决方案中使用RANK()(或DENSE_RANK()代替ROW_NUMBER()

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

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 TIESsource)。

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

答案 1 :(得分:1)

此解决方案确实使用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