简体   繁体   English

SQL 只有在 STUFF 中使用 Row_Number 值时查询速度慢

[英]SQL Query slow only when Row_Number values are used in STUFF

I have a fairly basic SQL query which runs in 1 second without the Data_1 field which is performing a STUFF() and using RN for filter and order.我有一个相当基本的 SQL 查询,它在 1 秒内运行,没有执行STUFF()并使用 RN 进行过滤和排序的Data_1字段。 With the Data_1 field in the query the execution goes from 1 second to 25 seconds.使用查询中的Data_1字段,执行时间从 1 秒变为 25 秒。 If I remove the RN filter and ORDER BY from the STUFF function of Data_1 then it goes back to executing in 1 second with both Data_1 and Data_2 in the query.如果我从 Data_1 的 STUFF function 中删除 RN 过滤器和 ORDER BY,那么它将返回到在 1 秒内执行,查询中同时包含 Data_1 和 Data_2。 So the issue seems to be with the RN piece within the STUFF.所以问题似乎出在 STUFF 中的 RN 部分。

Is there anything that can be done to make this run quickly without using temp tables?有什么办法可以在不使用临时表的情况下快速运行吗? The same thing works fine with temp tables instead of CTE but the requirement is to have this code in a view.同样的事情适用于临时表而不是 CTE,但要求是在视图中包含此代码。

There are only 350 total entries in the table and the result.表和结果中总共只有 350 个条目。 Running on MS SQL Server 2016 (13.0.7016.1)在 MS SQL Server 2016 (13.0.7016.1) 上运行

Note: Data_1 field requirement is to show 6 most recent updates per Program in the JSON string but in order from oldest to newest.注意:Data_1 字段要求在 JSON 字符串中显示每个程序的 6 个最新更新,但按从旧到新的顺序排列。 That's the only reason why I am using ROW_NUMBER because the underlying data can have alot more than 6 updates per Program.这是我使用 ROW_NUMBER 的唯一原因,因为每个程序的基础数据可能有超过 6 次更新。

WITH
CTE AS
    (SELECT P.Program_Number,
            P.Date_Status,
            '{"date":"' + P.Date_Status_Display + '","percent":"' + P.Percent_Complete + '","status":":' + P.Status_Overall_Col + '"}' AS JSON_String,
            ROW_NUMBER() OVER (PARTITION BY P.Program_Number ORDER BY P.Date_Status DESC) AS RN
     FROM dbo.Main_Entries_Table)
SELECT P.[Program_Number],
       P.[Program_Name],
       '[' + STUFF((SELECT ',' + [JSON_String]
                    FROM CTE C
                    WHERE C.Program_Number = P.Program_Number
                      AND RN <= 6
                    ORDER BY RN DESC
                   FOR XML PATH('')),1,1,'') + ']' AS Data_1,
       '[' + STUFF((SELECT ',' + [JSON_String]
                    FROM CTE C
                    WHERE C.Program_Number = P.Program_Number
                    ORDER BY Date_Status ASC
                   FOR XML PATH('')),1,1,'') + ']' AS Data_2,
       P.Last_Updated
FROM dbo.Main_Entries_Table P;

First, STRING_AGG will not help you here .首先, STRING_AGG不会在这里帮助您 The optimizer leverages the same tricks to concatenate the string either way.优化器利用相同的技巧以任何一种方式连接字符串。 STRING_AGG, however, is cleaner and handles conversions better but it would not solve this problem.然而,STRING_AGG 更干净并且可以更好地处理转换,但它不能解决这个问题。

Next, for a good answer you should include DDL and sample data like so.接下来,为了获得好的答案,您应该像这样包含 DDL 和示例数据。 This is what I'll use to show you what's up:这就是我将用来向您展示发生了什么的内容:

IF OBJECT_ID('tempdb..#Main_Entries_Table') IS NOT NULL DROP TABLE #Main_Entries_Table;

CREATE TABLE #Main_Entries_Table
(
  Program_Number      INT,
  [Program_Name]      VARCHAR(20),
  Date_Status         INT,
  Status_Overall_Col  INT,
  Percent_Complete    DECIMAL(4,2),
  Date_Status_Display VARCHAR(10)
);

INSERT #Main_Entries_Table
VALUES(1,'ABC',1,10,.1,'Yay!'),(1,'ABC',1,40,.95,'blah'),(1,'XYZ',0,10,.03,'NA'),
      (1,'ABC',3,44,.2,'Booo'),(1,'ABC',1,33,.35,'blah'),(1,'XYZ',0,999,.73,'NA'),
      (2,'RRR',1,10,.1,'Booo'),(2,'RRR',1,90,.44,'blah'),(2,'RRR',0,10,.03,'NA'),
      (2,'RRR',3,44,.2,'Booo'),(2,'RRR',1,93,.44,'blah'),(2,'RRR',0,55,.73,'NA');

Now lets look at your CTE query and the execution plan:现在让我们看看您的 CTE 查询和执行计划:

CTE Query Section CTE 查询部分

SELECT P.Program_Number,
        P.Date_Status,
        '{"date":"' + LEFT(P.Date_Status_Display,4) + '","percent":"' + LEFT(P.Percent_Complete,6) + 
        '","status":":' + LEFT(P.Status_Overall_Col,4) + '"}' 
          AS JSON_String,          
        ROW_NUMBER() OVER (PARTITION BY P.Program_Number ORDER BY P.Date_Status DESC) AS RN
FROM #Main_Entries_Table AS p

Execution plan:执行计划:

在此处输入图像描述

Depending on your data, that can be a big ol' expensive sort.根据您的数据,这可能是一种非常昂贵的类型。 This index will fix that:该索引将解决以下问题:

CREATE CLUSTERED INDEX idx_123 ON #Main_Entries_Table(Program_Number ASC, Date_Status DESC);

This ^^^ is what Itzik Ben-Gan calls a POC Index which stands for Partition, Order, Cover.这个 ^^^ 就是 Itzik Ben-Gan 所说的POC Index ,代表 Partition, Order, Cover。 This index handles the PARTITION BY clause first, then the 'ORDER BY' and, because it's clustered it covers all required columns.该索引首先处理PARTITION BY子句,然后是“ORDER BY”,并且因为它是聚簇的,所以它涵盖了所有必需的列。 You would likely have to create a non-clustered index with the correct covering columns.您可能必须创建一个具有正确覆盖列的非聚集索引。

New Execution plan:新的执行计划:

在此处输入图像描述

Now for your Data_2 column (excluding Data_1):现在为您的 Data_2 列(不包括 Data_1):

WITH
CTE AS
(
  SELECT P.Program_Number,
          P.Date_Status,
          '{"date":"' + LEFT(P.Date_Status_Display,4) + '","percent":"' + LEFT(P.Percent_Complete,6) + 
          '","status":":' + LEFT(P.Status_Overall_Col,4) + '"}' 
            AS JSON_String,          
        ROW_NUMBER() OVER (PARTITION BY P.Program_Number ORDER BY P.Date_Status DESC) AS RN
  FROM #Main_Entries_Table AS p
)
SELECT
P.[Program_Number],
       P.[Program_Name],
       '[' + STUFF((SELECT ',' + [JSON_String]
                    FROM CTE C
                    WHERE C.Program_Number = P.Program_Number
                    ORDER BY Date_Status ASC
                   FOR XML PATH('')),1,1,'') + ']' AS Data_2
FROM #Main_Entries_Table AS P;

Execution plan:执行计划:

在此处输入图像描述

Both queries (inside and outside the CTE) leverage the index to eliminate the sort AND to perform a seek against your rows (vs a scan which is slower).这两个查询(在 CTE 内部和外部)都利用索引来消除排序并针对您的行执行查找(与速度较慢的扫描相比)。 Now for your Data_1 column.现在为您的 Data_1 列。

WITH
CTE AS
(
  SELECT P.Program_Number,
          P.Date_Status,
          '{"date":"' + LEFT(P.Date_Status_Display,4) + '","percent":"' + LEFT(P.Percent_Complete,6) + 
          '","status":":' + LEFT(P.Status_Overall_Col,4) + '"}' 
            AS JSON_String,          
        ROW_NUMBER() OVER (PARTITION BY P.Program_Number ORDER BY P.Date_Status DESC) AS RN
  FROM #Main_Entries_Table AS p
)
SELECT
P.[Program_Number],
       P.[Program_Name],
       '[' + STUFF((SELECT ',' + [JSON_String]
                    FROM CTE C
                    WHERE C.Program_Number = P.Program_Number
                      AND RN <= 2
                    --ORDER BY RN DESC
                   FOR XML PATH('')),1,1,'') + ']' AS Data_1
FROM #Main_Entries_Table AS p;

Here you will get will get a sort and scan if you include the ORDER BY clause.如果您包含 ORDER BY 子句,您将在此处获得排序和扫描。 That said, you don't need it.也就是说,你不需要它。 With the aforementioned index in place, this will be quite fast:使用上述索引,这将非常快:

WITH
CTE AS
(
  SELECT P.Program_Number,
    P.Date_Status,
    '{"date":"' + LEFT(P.Date_Status_Display,4) + '","percent":"' + LEFT(P.Percent_Complete,6) + 
    '","status":":' + LEFT(P.Status_Overall_Col,4) + '"}' AS JSON_String,          
    ROW_NUMBER() OVER (PARTITION BY P.Program_Number ORDER BY P.Date_Status DESC) AS RN
  FROM #Main_Entries_Table AS p
)
SELECT
P.[Program_Number],
       P.[Program_Name],
       '[' + STUFF((SELECT ',' + [JSON_String]
                    FROM CTE C
                    WHERE C.Program_Number = P.Program_Number
                      AND RN <= 2
                   -- ORDER BY RN DESC
                   FOR XML PATH('')),1,1,'') + ']' AS Data_1,
       '[' + STUFF((SELECT ',' + [JSON_String]
                    FROM CTE C
                    WHERE C.Program_Number = P.Program_Number
                    ORDER BY Date_Status ASC
                   FOR XML PATH('')),1,1,'') + ']' AS Data_2
FROM #Main_Entries_Table AS P;

Check out the final plan:看看最终方案:

在此处输入图像描述

The key here is understanding how to analyze the execution plan data to tune your SQL.这里的关键是理解如何分析执行计划数据来调整你的 SQL。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM