1

SQLの関数を初めて使用しています。最適化に関するヘルプが必要です。私はSQL Server 2016を使用しています。sqlテーブル値関数を最適化する

私の関数は、1年ごとの異なるプロジェクトの従業員の計画時間と終了時間を比較してテーブル変数を返します。しかし、問題は、クエリが30-40秒間に約30,000行をロードしていることです。私は、クエリの最適化に関するいくつかのアドバイスを確認しましたが、私は自分のコードに間違ったものは見つけられません。それを最適化する方法についていくつかアドバイスをいただけますか?

全体機能コード:

CREATE FUNCTION dbo.fnProjectHours(      
@Project = '%',      
@Task = '%',      
@Year INT = 0         
)      

RETURNS @temp TABLE      
(      
Year INT, Month INT, Project VARCHAR(20), Task VARCHAR(20),      
User VARCHAR(50), PlannedHours Numeric(14,2),      
DoneHours Numeric(14,2) id int identity ,      
primary key(Year, Project, Task, Month, User, id)      
)       

AS      
BEGIN      

SELECT @Year= ISNULL(NULLIF(@Year,0),DATEPART(yy,GETDATE()));      
INSERT INTO @t      
(     
Year, Month, Project, Task, User, PlannedHours, DoneHours       
)      

    SELECT rbh.Year, rbh.Month, rbh.Project, rbh.Task, rbh.User, 
     rbhp.SumPlan AS PlannedHours, rbhw.SumDone AS DoneHours       

    FROM      
    (      
    SELECT      
    CASE      
    WHEN DATEPART(yy, ll.DateStart) IS NULL THEN rbhw.Year      
    ELSE DATEPART(yy, ll.DateStart)      
    END AS Year,      
    CASE      
    WHEN DATEPART(mm, ll.DateStart) IS NULL THEN DATEPART(mm, rbhw.Date)      
    ELSE DATEPART(mm, ll.DateStart)      
    END AS Month,      
    dbo.wusr_fn_cut(mn.Number, '/') AS Project, ml.Task,        
    ISNULL(ll.Login,rbhw.Login) AS User     

    FROM dbo.Nag AS mn WITH (nolock)           
    INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID           
    INNER JOIN dbo.LinLogin AS ll WITH (NOLOCK) ON ll.ID = ml.ID      
    AND ll.LinId = ml.LinId  
    INNER JOIN dbo.sl_Operator AS o WITH (Nolock) ON ll.Login = o.Login                    
    FULL OUTER JOIN dbo.Hours AS rbhw WITH (NOLOCK)      
    ON dbo.wusr_fn_cut(mn.Number, '/') = rbhw.Project AND ml.Task = rbhw.Task      
    AND ll.Login = rbhw.Login AND DATEPART(yy, ll.DateStart) = DATEPART(yy, rbhw.Date)    
    AND DATEPART(mm, ll.DateStart) = DATEPART(mm, rbhw.Date)      

    WHERE (mn.Number IS NOT NULL) AND (mn.Status = 0) AND dbo.wusr_fn_cut(mn.Number, '/') LIKE @Project   
AND ml.Task LIKE @Task          

    UNION ALL        

    SELECT      
    CASE      
    WHEN DATEPART(yy, ll.DateStart) IS NULL THEN rbhw.Year      
    ELSE DATEPART(yy, ll.DateStart)      
    END AS Year,      
    CASE      
    WHEN DATEPART(mm, ll.DateStart) IS NULL THEN DATEPART(mm, rbhw.Date)      
    ELSE DATEPART(mm, ll.DateStart)      
    END AS Month,      
    rbhw.Project, rbhw.Task,       
    ISNULL(ll.Login,rbhw.Login) AS User     

    FROM dbo.Nag AS mn WITH (nolock)           
    INNER JOIN dbo.Hours AS rbhw WITH (NOLOCK)      
    ON dbo.wusr_fn_cut(mn.Number, '/') = rbhw.Project           
    INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID      
    AND rbhw.Task = ml.Task          
    INNER JOIN dbo.Operator AS o WITH (Nolock) ON rbhw.Login = o.Login           
    FULL OUTER JOIN dbo.LinLogin AS ll WITH (nolock) ON mn.ID = ll.ID      
    AND ml.LinId = ll.LinId AND o.Login = ll.Login      
    AND DATEPART(yy, rbhw.Date)=DATEPART(yy, ll.DateStart)      
    AND DATEPART(mm, rbhw.Date) = DATEPART(mm, ll.DateStart)      

    WHERE (rbhw.Project IS NOT NULL) AND (mn.Status = 0) AND (DATEPART(mm, ll.DateStart) IS NULL) AND rbhw.Project LIKE @Project   
    AND rbhw.Task LIKE @Task      
) AS rbh    

    LEFT JOIN    
    (     
    SELECT DATEPART(yy, ll.DateStart) AS Year, DATEPART(mm, ll.DateStart) AS Month,      
    dbo.wusr_fn_cut(mn.Number, '/') AS Project, ml.Task AS Task,       
    ll.Login AS LoginLL, SUM(ll.Hours) AS SumPlan    

    FROM dbo.Nag AS mn WITH (nolock)           
    INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID           
    INNER JOIN dbo.LinLogin AS ll WITH (NOLOCK) ON ll.ID = ml.ID      
    AND ll.LinId = ml.LinId     

    WHERE mn.Status=0        
    GROUP BY DATEPART(yy, ll.DateStart),DATEPART(mm, ll.DateStart),dbo.wusr_fn_cut(mn.Number, '/'),ml.Task,ll.Login     
) AS rbhp     
    ON rbh.Project=rbhp.Project AND rbh.Task=rbhp.Task AND      
    rbh.Year=rbhp.Year AND rbh.Month=rbhp.Month AND rbh.User=rbhp.LoginLL    

    LEFT JOIN    
    (     
    SELECT h.Year, DATEPART(mm, h.Date) AS Month,h.Project AS Project, h.Task AS Task,      
    h.Login AS LoginRbhw, SUM(h.Hours) AS DoneSum    

    FROM dbo.Nag AS mn WITH (nolock)           
    INNER JOIN dbo.Lin AS ml WITH (NOLOCK) ON mn.ID = ml.ID             
    INNER JOIN dbo.Hours AS h WITH (NOLOCK) ON dbo.wusr_fn_cut(mn.Number, '/') = h.Project           
    AND ml.Task = h.Task    

    WHERE mn.Status=0       
    GROUP BY h.Year,DATEPART(mm, h.Date),h.Project,h.Task,h.Login     
) AS rbhw     
    ON rbh.Project=rbhw.Project AND rbh.Task=rbhw.Task AND      
    rbh.Year=rbhw.Year AND rbh.Month=rbhw.Month AND rbh.User=rbhw.LoginRbhw    

WHERE rbh.Month IS NOT NULL AND [email protected]    

    GROUP BY rbh.Year, rbh.Month, rbh.Project, rbh.Task, rbh.User,rbhp.SumPlan, rbhw.DoneSum    

    ORDER BY rbh.Project, rbh.Task, rbh.User, rbh.Month   

RETURN      
END  

rbhサブクエリが最初LEFT JOINをプロジェクト番号のような列のほとんどの値は、ユーザデータ等

rbhp)は時間の和を取得する取得をそのユーザーは、明確なタスクと月のプロジェクトに費やす予定です(返品表の列PlannedHours)。

2番目のLEFT JOIN(rbhw)は、ユーザーが特定のタスクと月に実際にプロジェクトに費やした時間の合計(返信表の列DoneHours)を取得します。

+0

関数をインラインテーブル値関数に変換します。パフォーマンスが向上する可能性があります。 –

+0

これは膨大なクエリです。ソーステーブルはどのようになっていますか?最初にインデックスを最適化することから始めます。テーブルを結合するために使用しているフィールドがインデックスに登録されていることを確認します。 –

+2

SSMSでは、[実際の実行計画を含める]をオンにします。内側のクエリ( 'SELECT'部分)を手動で実行し(適切に変数を宣言して設定する)、クエリプランを見てください。ほとんどの場合、非常に大胆な矢印が表示されている可能性があります。これは、部品を照会すること、最適化が必要なことを指摘しています。多くの場合、索引を作成したり、ロジックを少し変更したりする必要があります。 'JOIN'の条件でカスタム関数を使うことはパフォーマンスキラーでもあります。 – Arvo

答えて

0

ここに問題があります。テーブル変数を宣言し、複合主キーを作成してから、そのテーブル変数にINSERTを実行しました。

最初に、DML操作をたくさん行うと、クラスタード・インデックスは適切なオプションではありません。

第2に、id列(ID列)がすでに存在していますが、これらの複合列がまだプライマリキーとして必要な理由があります。

私は、必要でない場合は、その複合主キーを削除することができますし、あなたのパフォーマンスをかなり向上させると思います。

+0

Unfrftunatelyそれは助けていませんでした:(おそらく最大の問題はselect文の中です。 – boogie

0

実際の計画に表示されるコストは、推定行数に基づく推定値であり、完全に間違っている可能性があります。最低限のI/Oを引き起こすテーブルを確認するには、少なくともset statistics ioを使用してください。

スカラー関数は実際にはあまりにも頻繁に悪いので、あなたはwusr_fn_cutを使用しています。クエリプランや統計情報にパフォーマンスの影響は見られません。たとえば、プランキャッシュを使用する必要があります。

インデックススプールとは、SQL Serverが一時インデックスを作成したことを意味します。 dbは使用するのに十分なインデックスがなかったためです。それが見える場合は、元のテーブルのインデックスを作成して、スプールを必要としないようにしてください。

この種のwhere句は、非sargableなので、非常に悪いです。

AND DATEPART(yy, rbhw.Date)=DATEPART(yy, ll.DateStart)      
AND DATEPART(mm, rbhw.Date) = DATEPART(mm, ll.DateStart) 

あなたはいつものパラメータのデフォルト値日付> = XXXおよび日付< = YYY

のように、関数を使用せずに、実際の値を比較してみてください:

@Project = '%',      
@Task = '%', 

は、ことを示唆しています%をプロジェクトやタスクなどに制限したくないときに渡しています。これはパラメータスニッフィングの状況が非常に簡単になることがあります。別のケースに合わせて最適化されているため、完全に間違ったプランを使用する場合もあります。同様のスカラー関数でパラメータを使用しているため、いずれにしても非常に良いインデックス使用率を持つことになります。クエリを分割してtempを使用すると役立ちます。データベースの理解と動作のテストが必要です。

関連する問題