web-dev-qa-db-ja.com

CTE使用時の最適化

採用された各営業担当者の集計データを取得して、Salesnamesaleamount、およびsaleshippingamountを表示します。 2 UPDATE CTEステートメントを使用して、各従業員のマスターデータを格納するテーブルを更新するようにクエリを設定しています。合計を表示する必要があるため、更新しています。

構文が完了するまでに約10分かかり、2つのテーブルの間に処理する行が約60,000行あります。 CTEはこれについて間違った方法ですか?

以下はサンプルDDLです。従業員名はHRテーブルから入力されますが、これについてはDDLを表示せず、挿入ステートメントのみを示しました。

Create Table #FinalData
(
    employee varchar(400)
    ,tr float
    ,tf float
)
Create Table #TR
(
    employee varchar(400)
    ,saleamount float
    ,saledate date
)
Create Table #TF
(
    employee varchar(400)
    ,saleshippingamt float
    ,saledate date
)
INSERT INTO #TR (employee, saleamount, saledate) VALUES
('Employee 1', '12.63', '2017-01-01'), ('Employee 1', '15.00' ,'2017-01-02')
,('Employee 2', '14.00', '2017-01-03'), ('Employee 2' ,'12.00', '2017-01-03')
,('Employee 3', '16.00', '2017-01-03'), ('Employee 3', '13.00', '2017-01-04')
INSERT INTO #TF (employee, saleshippingamt, saledate) VALUES
('Employee 1', '5.00', '2017-01-01'), ('Employee 1', '6.00' ,'2017-01-02')
,('Employee 2', '5.50', '2017-01-03'), ('Employee 2' ,'5.00', '2017-01-03')
,('Employee 3', '6.00', '2017-01-03'), ('Employee 3', '6.00', '2017-01-04')
INSERT INTO #FINALDATA (employee) Values
('Employee 1'), ('Employee 2'), ('Employee 3')

;With TR As 
(
    SELECT 
    employee
    ,SUM(saleamount) Totals
    FROM #TR
    WHERE saledate BETWEEN '2017-01-01' AND '2017-01-25'
    GROUP BY employee
)
UPDATE t
SET tr = t2.TotalCount
FROM #FinalData t
INNER JOIN (Select employee, SUM(Totals) TotalCount
            FROM TR
            GROUP BY employee) As t2
ON t.employee = t2.employee

;With TF As 
(
    SELECT 
    employee
    ,SUM(saleshippingamt) Totals
    FROM #TF
    WHERE saledate BETWEEN '2017-01-01' AND '2017-01-25'
    GROUP BY employee
)
UPDATE t
SET tf = t2.TotalCount
FROM #FinalData t
INNER JOIN (Select employee, SUM(Totals) TotalCount
            FROM TF
            GROUP BY employee) As t2
ON t.employee = t2.employee

Select * FROM #FinalData

そして、これが私の 実行計画 です。

このクエリを最適化するために必要なオプションは何ですか?

ようやくテストとクエリの操作ができるようになり、より具体的な提案ができるようになりました。サンプルにはAdventureWorksを使用したので、実際に使用するデータがいくつかありました。

オプション1-インデックスを追加する

これにより、最小限の労力で最高のパフォーマンスが向上します。 3つのインデックスを追加するだけで、サンプルセットのクエリ実行が40%増加しました。

テーブル#TRおよび#TFsaledateemployeeにインデックスを追加し、金額を含めます。

CREATE INDEX IDXNC_TFSaleDateEmployee ON #TF (saledate, employee) INCLUDE (saleshippingamt)

CREATE INDEX IDXNC_TRSaleDateEmployee ON #TR (saledate, employee) INCLUDE (saleamount)

#FinalDataの場合、employeeフィールドにクラスター化インデックスが必要です。

CREATE CLUSTERED INDEX IDXC_FinalDataEmployee ON #FinalData (employee)

オプション2-クエリを変更する

クエリでは微調整を使用できます。一時テーブルをまとめて削除することもできます。

このクエリも少し高速に実行されますが、インデックスを使用するとパフォーマンスが最も向上します。

;WITH CTE_Employee AS
    (
    SELECT DISTINCT employee
    FROM (
        SELECT employee
        FROM #TR
        UNION ALL
        SELECT employee
        FROM #TF
        ) AS D
    )
    , CTE_TR AS
    (
    SELECT employee
        , SUM(saleamount) AS TotalDue
    FROM #TR
    WHERE saledate BETWEEN '2017-01-01' AND '2017-01-25'
    GROUP BY employee
    )
    , CTE_TF AS
    (
    SELECT employee
        , SUM(saleamount) AS TotalDue
    FROM #TR
    WHERE saledate BETWEEN '2017-01-01' AND '2017-01-25'
    GROUP BY employee
    )

SELECT S.employee
    , TR.TotalDue
    , TF.TotalDue
FROM CTE_Employee AS S
    LEFT OUTER JOIN CTE_TR AS TR ON TR.employee = S.employee
    LEFT OUTER JOIN CTE_TF AS TF ON TF.employee = S.employee

一般的なコメント

CTEには好きな名前を付けることができますが、CTEが表すテーブルと同じ名前を付けると少し混乱します。 CTE_*プレフィックスを付けたいのですが、これはオプションです。

ここで行ったように、CTEをチェーンすることもできます。

元のクエリでは、#FinalDatatotalcountの合計を取得するときに、2つの集計演算を実行していました。これも注意が必要です。不要であることがわかりますが、SQLは並べ替えと合計の操作を複数回実行します。副選択を行う代わりに、CTEテーブルを結合するだけで済みます。

あなたが持っていた

UPDATE t
SET tf = t2.TotalCount
FROM #FinalData t
INNER JOIN (Select employee, SUM(Totals) TotalCount
            FROM TF
            GROUP BY employee) As t2
ON t.employee = t2.employee

する必要があります

UPDATE t
SET tf = t2.TotalCount
FROM #FinalData t
INNER JOIN TF As t2 ON t.employee = t2.employee
1
Jonathan Fite

ダミーデータをポストしますが、常に実際のテーブル構造をポストします。これは、実際のビューを取得するのに役立ちます。たとえば、emloyee varchar(400)を使用した#FinalDataは現実的ではないと思います。

IMHO、varchar(400)でCIまたは任意のインデックスを作成することは非常に悪いため、インデックスのメリットはありません。

varchar(400)は、インデックスのみをカバーするのに最適です。

したがって、実際にそのような状況が実際にある場合は、可能な限り最適化されたクエリを作成することに集中します。より速い結果を得るためにページングなどを適用する場合があります。

そのような状況ではインデックスを作成しません。

したがって、あなたが投稿したデータと構造に従って、ハードコードの代わりに変数を使用します。

内部結合が機能しない場合は、左結合に進みます

select fd.employee
,tr.Totals
 ,tf.shippingamtTotals
 from 
#FinalData FD
inner JOIN
(
 SELECT 
    employee
    ,SUM(saleamount) Totals
    FROM #TR
    WHERE saledate BETWEEN '2017-01-01' AND '2017-01-25'
    GROUP BY employee
)tr
on fd.employee=tr.employee
inner join

(
SELECT 
    employee
    ,SUM(saleshippingamt) shippingamtTotals
    FROM #TF
    WHERE saledate BETWEEN '2017-01-01' AND '2017-01-25'
    GROUP BY employee
)tf
on fd.employee=tf.employee
0
KumarHarsh