左外连接 - 查询计划中的排序操作 - 有什么方法可以调整这个简单的查询?

Mar*_*lli 4 performance sql-server optimization execution-plan sort-operator query-performance

在处理以下查询以回答此问题时:

如何以数据库不可知的方式查询图表数据?

有以下表格:

CREATE TABLE [dbo].[#foo] ( 
[creation]  DATETIME                         NOT NULL,
[value]     MONEY                                NULL,
[DT]        AS (CONVERT([date],[CREATION])) PERSISTED)


-- add a clustered index on the dt column
CREATE CLUSTERED INDEX CI_FOO ON #FOO(DT)
GO
Run Code Online (Sandbox Code Playgroud)

和另一个加入表:

create table #bar (dt date primary key clustered)
go
Run Code Online (Sandbox Code Playgroud)

可以在此处找到将数据加载到这些表中

但是在运行以下查询时:

WITH RADHE AS (
SELECT THE_ROW=ROW_NUMBER() OVER(PARTITION BY B.DT ORDER BY B.DT),
       THE_DATE=B.dt,
       THE_NUMBER_OF_RECORDS_ON_THIS_DAY=CASE WHEN F.DT IS NULL THEN 0 ELSE COUNT(*) OVER (PARTITION BY F.DT ) END ,
       THE_TOTAL_VALUE_FOR_THE_DAY=COALESCE(SUM(F.VALUE) OVER (PARTITION BY b.DT ),0)

FROM #BAR B
LEFT OUTER JOIN #FOO F
ON B.dt = F.dt
)

--get rid of the duplicates and present the result
SELECT 
THE_DATE,
THE_NUMBER_OF_RECORDS_ON_THIS_DAY,
THE_TOTAL_VALUE_FOR_THE_DAY
FROM RADHE
WHERE THE_ROW = 1
Run Code Online (Sandbox Code Playgroud)

我得到了类似下面这张图片的东西,这正是我要找的。

在此处输入图片说明

但是生成的执行计划有几个排序和嵌套循环操作,如下图所示。

完整的查询计划可以在这里找到。

在此处输入图片说明

这是一个非常简单的操作,两个表之间的左外连接,索引已经排序,因此我想知道是否可以简化查询计划。

或者,我可以更改查询代码。

为什么我们在查询计划中需要nested loops2 次和sort2 次?

Mar*_*ith 8

您有一个提供排序依据的索引,B.DT但是

  • 计划首先THE_ROW使用此顺序进行评估
  • 然后右手排序顺序F.DT进行评估THE_NUMBER_OF_RECORDS_ON_THIS_DAY
  • 终于左手排序放的东西放回B.DT为了使THE_TOTAL_VALUE_FOR_THE_DAY

您可以通过简单地更改 CTE 中列的顺序来摆脱其中一种排序,使F.DT最后一个出现(此不必要排序的连接项在此处

WITH RADHE AS (
SELECT THE_ROW=ROW_NUMBER() OVER(PARTITION BY B.DT ORDER BY B.DT),
       THE_DATE=B.dt ,
       THE_TOTAL_VALUE_FOR_THE_DAY=COALESCE(SUM(F.VALUE) OVER (PARTITION BY b.DT ),0),
       THE_NUMBER_OF_RECORDS_ON_THIS_DAY=CASE WHEN F.DT IS NULL THEN 0 ELSE COUNT(*) OVER (PARTITION BY F.DT ) END

FROM #BAR B
LEFT OUTER JOIN #FOO F
ON B.dt = F.dt
)
Run Code Online (Sandbox Code Playgroud)

但是您可以通过将定义更改THE_NUMBER_OF_RECORDS_ON_THIS_DAY

CASE WHEN F.DT IS NULL THEN 0 ELSE COUNT(*) OVER (PARTITION BY B.DT ) END
Run Code Online (Sandbox Code Playgroud)

因此它使用与其余函数相同的分区定义。

这不应该改变您的示例中的任何内容,因为您的CASE表达式0无论如何只会分配给任何不匹配的行。

至于计划的其余部分,请参阅分区和公共子表达式假脱机

(事后计划,没有排序)

在此处输入图片说明


pap*_*zzo 5

我认为你让这件事变得比它需要的要困难得多

SELECT THE_DATE = B.dt,
       THE_NUMBER_OF_RECORDS_ON_THIS_DAY = COUNT(F.DT),
       THE_TOTAL_VALUE_FOR_THE_DAY = SUM(ISNULL(F.VALUE, 0))
FROM   #BAR B
       LEFT OUTER JOIN #FOO F
         ON B.dt = F.dt
GROUP  BY B.dt 
Run Code Online (Sandbox Code Playgroud)