web-dev-qa-db-ja.com

左外部結合-クエリプランでの並べ替え操作-この単純なクエリをチューニングする方法はありますか?

この質問に答えるために以下のクエリに取り組んでいる間:

データベースにとらわれない方法でグラフデータをクエリする方法

次のテーブルがあります。

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

参加のためのこの他のテーブル:

create table #bar (dt date primary key clustered)
go

これらのテーブルへのデータのロードはここにあります

しかし、次のクエリを実行すると:

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

下の写真のようになりました。これはまさに私が探していたものです。

enter image description here

ただし、以下の図に示すように、生成された実行プランにはいくつかのソートおよびネストされたループ操作があります。

完全なクエリプランはここにあります。

enter image description here

これは非常に単純な操作で、2つのテーブル間の左外部結合であり、インデックスは既に順序付けされているため、クエリプランを簡略化できるかどうか疑問に思っていました。

または、クエリコードを変更することもできます。

なぜ正確にnested loopsクエリプランで2回とsort 2回ですか?

4

B.DTによる順序付けを提供するインデックスがありますが、

  • プランは最初にこの順序を使用してTHE_ROWを評価します
  • 次に、右側をF.DTで並べ替えて、THE_NUMBER_OF_RECORDS_ON_THIS_DAYを評価します
  • 最後に、左側の並べ替えでB.DTTHE_TOTAL_VALUE_FOR_THE_DAY順に並べ替えます。

CTEの列の順序を変更するだけで、並べ替えの1つを取り除くことができるので、F.DT 1が最後に表示されます( この不要な並べ替えの接続項目はここにあります

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
)

ただし、THE_NUMBER_OF_RECORDS_ON_THIS_DAYの定義を次のように変更することで、両方を取り除くことができます。

CASE WHEN F.DT IS NULL THEN 0 ELSE COUNT(*) OVER (PARTITION BY B.DT ) END

したがって、他の関数と同じパーティション定義を使用します。

CASE式はとにかく一致しない行に0を割り当てるだけなので、この例では何も変更されません。

残りの計画については パーティショニングと共通部分式スプール を参照してください

(後でソートなしで計画)

enter image description here

8
Martin Smith

あなたはこれを必要以上に難しくしていると思います

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 
5
paparazzo