查询性能:CTE 使用 ROW_NUMBER() 选择第一行

Posted

技术标签:

【中文标题】查询性能:CTE 使用 ROW_NUMBER() 选择第一行【英文标题】:Query performance: CTE using ROW_NUMBER() to select first row 【发布时间】:2021-03-05 15:21:40 【问题描述】:

我们有三个环境,当我在其中两个环境中运行我的 SQL 查询时,只需要 30 或 38 秒即可运行,但在另一个环境中运行未完成,我应该取消它。查询基于两部分,一个 CTE 和一个非常简单的从表中选择,在 CTE 和选择中我都使用同一个表。

你能告诉我为什么需要这么长时间吗?如何改进查询?

ALTER VIEW [fact].[vPurchase] 
AS
    WITH VKPL AS 
    (
        SELECT * 
        FROM
            (SELECT 
                 iv.[Delivery_FK],
                 1 AS column2,
                 ROW_NUMBER() OVER(PARTITION BY [Delivery_FK] ORDER BY iv.UpdateDate) AS rk     
             FROM 
                 [fact].[KRMFact] iv   
             LEFT JOIN 
                 [dimension].[Product] pr ON iv.Product_FK =pr.Product_SK
             LEFT JOIN 
                 [dimension].[Delivery] le ON le.Delivery_FK = iv.Delivery_FK 
             WHERE 
                 pr.Product_Key = '740') X
        WHERE 
            rk = 1
    )
    SELECT 
         -- ....  here are some columns
         Delivery_FK,
         Product_FK,
         CAST(column2 AS VARCHAR) AS column2,
         f.[UpdateDate] AS [Update date]
     FROM 
         [fact].[KRMFact] f
     LEFT JOIN 
         VKPL v ON f.Delivery_FK = v.Delivery_FK

【问题讨论】:

检查您的执行计划和索引。此外,不同的环境具有不同的数据量,这会影响性能。 Bad Habits to Kick : Declaring VARCHAR without (length) 您对“pr”的外部连接被您在 where 子句中对 pr.Product_Key 的引用所破坏。外部连接到交付(“le”)但不引用任何列是奇怪的。在 cte 中使用整数常量 (1) 但强制转换为字符串也很奇怪 - 为什么不在 cte 中使用字符串常量(适当大小)? “这里有一些列”告诉我这绝不是整个查询。而且您出于完全未知的原因进行了自我加入 请read this 然后edit 您的问题包含更多详细信息。此外,SQL Server Management Studio (SSMS) 具有向您显示查询执行计划的功能。将您的查询放入 SSMS,右键单击并选择“包括实际执行计划”。然后运行查询。执行计划显示可能会建议您创建一个索引,以使该查询运行得更快。 【参考方案1】:

这是猜测。

    我猜这个查询速度慢的环境是其中有大量生产数据的环境。

    我想你的KRMFact 表上的一些索引也许会对你有所帮助。以下是确定您需要什么的方法: SQL Server Management Studio (SSMS) 具有向您显示查询执行计划的功能。将您的查询(请不要简化,实际查询)放入 SSMS,右键单击并选择“包括实际执行计划”。然后运行查询。执行计划显示可能会建议您创建一个索引,以使该查询运行得更快。

    我猜您正在尝试查找最早值为 UpdateDate 的行。

您的子查询

SELECT * 
  FROM
      (SELECT 
              iv.[Delivery_FK],
              1 AS column2,
              ROW_NUMBER() OVER(PARTITION BY [Delivery_FK] ORDER BY iv.UpdateDate) AS rk     
        FROM 
              [fact].[KRMFact] iv   
        LEFT JOIN 
             [dimension].[Product] pr ON iv.Product_FK =pr.Product_SK
        LEFT JOIN 
             [dimension].[Delivery] le ON le.Delivery_FK = iv.Delivery_FK 
       WHERE 
             pr.Product_Key = '740') X
 WHERE 
       rk = 1

看起来它为KRMFact.Delivery_FK 的每个值挑选出最早KRMFact.UpdateDate 的行。这就是ROW_NUMBER() OVER... WHERE rk=1 语言的作用。

如果我的猜测是正确的,你可以用不同的方式来做,这可能更有效。

SELECT * 
  FROM
      (SELECT 
              iv.[Delivery_FK],
              1 AS column2,
              1 AS rk
        FROM 
              [fact].[KRMFact] iv   
        JOIN (   SELECT Delivery_FK, MIN(UpdateDate) first_update
                   FROM [fact].[KRMFact]
                 GROUP BY Delivery_FK
             ) first_update ON iv.UpdateDate = first_update.first_update
        LEFT JOIN 
             [dimension].[Product] pr ON iv.Product_FK =pr.Product_SK
        LEFT JOIN 
             [dimension].[Delivery] le ON le.Delivery_FK = iv.Delivery_FK 
       WHERE 
             pr.Product_Key = '740') X
 WHERE 
       rk = 1

您可能应该尝试新旧版本的子查询,以确定它们是否会产生相同的结果。

如果你使用我建议的这个子查询查询,这个索引将通过优化新的子查询的MIN() ... GROUP BY 操作来帮助它运行得更快。

CREATE INDEX x_KRMFact_Product_Update 
          ON [fact].[KRMFact]
             ([Product_FK],[UpdateDate])

顺便说一句,WHERE pr.Product_Key = '740' 将您的 LEFT JOIN [dimension].[Product] 操作变成了普通的内部 JOIN。

【讨论】:

感谢您的回复。我使用了您建议的sql,但得到了相同的结果。我通过为 CTE 创建一个单独的 SQL 视图并将其加入到主视图中解决了这个问题。

以上是关于查询性能:CTE 使用 ROW_NUMBER() 选择第一行的主要内容,如果未能解决你的问题,请参考以下文章

分区上的递归 CTE 或 ROW_NUMBER?

row_number 和 cte 使用实例:考场监考安排

从CTE删除时出错

CTE、ROW_NUMBER 和 ROWCOUNT

将 with cte 查询的结果插入临时表

递归 CTE - row_number() 聚合