web-dev-qa-db-ja.com

SQL Server:CaseステートメントでCTEパフォーマンスを向上させる

私はあなたの助けが必要です、次の与えられたビューのパフォーマンスを改善するためにいくつかのガイダンスが必要です。

私は以下のコードで書かれたビューを持っています:

with timeframes  as
(
select p.SEARCH_NUM, 
case   when p.FROM_DATE is not null then p.FROM_DATE
       when p.FROM_DATE is null and P.SEARCH_DAYS is not null and p.TO_DATE is not null then DATEADD(day,p.SEARCH_DAYS*-1,p.TO_DATE)
       when p.FROM_DATE is null and P.SEARCH_DAYS is not null and p.TO_DATE is null then DATEADD(day,p.SEARCH_DAYS*-1,GetDate()) 
       when p.FROM_DATE is null and P.SEARCH_DAYS is null and p.TO_DATE is not null and p.DURATION = 'Yearly' then DATEADD(year,-1,p.TO_DATE)
       when p.FROM_DATE is null and P.SEARCH_DAYS is null and p.TO_DATE is null and p.DURATION = 'Yearly' then DATEADD(year,-1,GetDate())
       when p.FROM_DATE is null and P.SEARCH_DAYS is null and p.TO_DATE is not null and p.DURATION = 'Monthly' then DATEADD(month,-1,p.TO_DATE)
       when p.FROM_DATE is null and P.SEARCH_DAYS is null and p.TO_DATE is null and p.DURATION = 'Monthly' then DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0)
       when p.FROM_DATE is null and P.SEARCH_DAYS is null and p.TO_DATE is not null and p.DURATION = 'Weekly' then DATEADD(day,-7,p.TO_DATE)
       when p.FROM_DATE is null and P.SEARCH_DAYS is null and p.TO_DATE is null and p.DURATION = 'Weekly' then DATEADD(day,-7,GetDate())
       else DATEADD(month,-1,GetDate())
  end as FROM_DATE
  ,case when p.TO_DATE is not null then p.TO_DATE
    when p.TO_DATE is null and p.DURATION = 'Monthly' then DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1)
       else GetDate()
  end as TO_DATE
from dbo.parmeters_table as p
)
,
transactions as
(
  select tm.SEARCH_NUM, tr.id from dbo.ixf_transaction tr
  inner join timeframes tm on tr.transaction_date between tm.FROM_DATE and tm.TO_DATE
)
,
searchResults AS
(
select DISTINCT
  t.SEARCH_NUM
  ,trx.id as 'trx_id_FK'
  ,trx.institution_FK
  ,trx.branch_FK
  ,branch.code as 'branch_number'
  ,trx.account_FK
  ,trx.transaction_date as 'trx_date'
  ,case when trx.type_of_transaction = 'I' or trx.type_of_transaction = 'B'
    then trx.base_currency_amount else 0 end as 'cash_in'
  ,case when trx.type_of_transaction = 'O' or trx.type_of_transaction = 'B'
    then trx.base_currency_amount else 0 end as 'cash_out'  
  ,case when trx.type_of_transaction = 'B'
    then trx.base_currency_amount else 0 end as 'curr_exchange'  
  ,trx.base_currency_amount    
  ,trx.type_of_transaction
  ,trx.teller_id
  ,trx.unique_trans_id
  ,trx.serial_number
  ,trx.foreign_amount
  ,trx.country_of_currency_FK
  ,trx.flex_1
  ,trx.flex_2
  ,trx.customer_FK
  ,ttr.code as 'trx_code'
  ,ttr.description as 'trx_description'
  ,ttr.irs_transaction_id
 ,cust.full_name 
 ,str(cust.web_reference_id) as 'web_reference_id' 
 ,conben.customer_cif 
 ,conben.id_number 
 ,conben.id_type
 ,conben.other_description as 'id_type_other_description'
 ,conben.customer_tin 
 ,conben.cust_type_fk as 'TIN_Type'
 ,ctrtx.is_teller
from ixf_transaction trx
  inner join transactions t on trx.id = t.id
  inner join type_ref ttr on ttr.id=trx.transaction_type_FK
  inner join unit branch on branch.id=trx.branch_FK and branch.object_type='Branch'
  left join  cust on cust.id = trx.customer_FK
  left join  cashtx_cus conben on conben.transaction_id = trx.unique_trans_id    and conben.customer_FK = trx.customer_FK
  left join  trans ctrtx on ctrtx.cash_transaction_fk = trx.id
) 

  select trx_id_fk 
  ,case when account_FK is not null then (select count(1) from searchresults a where a.account_fk = searchresults.account_fk and a.SEARCH_NUM = searchresults.SEARCH_NUM) 
   when customer_cif is not null then (select count(1) from searchresults a where a.customer_cif = searchresults.customer_cif and a.SEARCH_NUM = searchresults.SEARCH_NUM) 
   when customer_tin is not null then (select count(1) from searchresults a where a.customer_tin = searchresults.customer_tin and a.TIN_Type = searchresults.TIN_Type and a.SEARCH_NUM = searchresults.SEARCH_NUM) 
   when ID_NUMBER is not null then (select count(1) from searchresults a where a.ID_NUMBER = searchresults.ID_NUMBER and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   else (select count(1) from searchresults a where a.SEARCH_NUM = searchresults.SEARCH_NUM)
   end as 'trx_per_period'
  ,case when account_FK is not null then (select count(1) from searchresults a where a.account_FK = searchresults.account_FK and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM) 
   when customer_cif is not null then (select count(1) from searchresults a where a.customer_cif = searchresults.customer_cif and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM) 
   when customer_tin is not null then (select count(1) from searchresults a where a.customer_tin = searchresults.customer_tin and a.TIN_Type = searchresults.TIN_Type and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when ID_NUMBER is not null then (select count(1) from searchresults a where a.ID_NUMBER = searchresults.ID_NUMBER and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   else (select count(1) from searchresults a where a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   end as 'trx_per_day'
  ,case when account_FK is not null then (select sum(cash_in) from searchresults a where a.account_FK = searchresults.account_FK and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_cif is not null then (select sum(cash_in) from searchresults a where a.customer_cif = searchresults.customer_cif and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_tin is not null then (select sum(cash_in) from searchresults a where a.customer_tin = searchresults.customer_tin and a.TIN_Type = searchresults.TIN_Type and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when ID_NUMBER is not null then (select sum(cash_in) from searchresults a where a.ID_NUMBER = searchresults.ID_NUMBER and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   else (select sum(cash_in) from searchresults a where a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   end as 'total_per_day_cash_in'
  ,case when account_FK is not null then (select sum(cash_out) from searchresults a where a.account_FK = searchresults.account_FK and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_cif is not null then (select sum(cash_out) from searchresults a where a.customer_cif = searchresults.customer_cif and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_tin is not null then (select sum(cash_out) from searchresults a where a.customer_tin = searchresults.customer_tin and a.TIN_Type = searchresults.TIN_Type and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when ID_NUMBER is not null then (select sum(cash_out) from searchresults a where a.ID_NUMBER = searchresults.ID_NUMBER and a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   else (select sum(cash_out) from searchresults a where a.trx_date = searchresults.trx_date and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   end as 'total_per_day_cash_out'
  ,case when account_FK is not null then (select sum(cash_in) from searchresults a where a.account_FK = searchresults.account_FK and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_cif is not null then (select sum(cash_in) from searchresults a where a.customer_cif = searchresults.customer_cif and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_tin is not null then (select sum(cash_in) from searchresults a where a.customer_tin = searchresults.customer_tin and a.TIN_Type = searchresults.TIN_Type and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when ID_NUMBER is not null then (select sum(cash_in) from searchresults a where a.ID_NUMBER = searchresults.ID_NUMBER and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   else (select sum(cash_in) from searchresults a where a.SEARCH_NUM = searchresults.SEARCH_NUM)
   end as 'total_per_period_cash_in'
  ,case when account_FK is not null then (select sum(cash_out) from searchresults a where a.account_FK = searchresults.account_FK and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_cif is not null then (select sum(cash_out) from searchresults a where a.customer_cif = searchresults.customer_cif and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when customer_tin is not null then (select sum(cash_out) from searchresults a where a.customer_tin = searchresults.customer_tin and a.TIN_Type = searchresults.TIN_Type and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   when ID_NUMBER is not null then (select sum(cash_out) from searchresults a where a.ID_NUMBER = searchresults.ID_NUMBER and a.SEARCH_NUM = searchresults.SEARCH_NUM)
   else (select sum(cash_out) from searchresults a where a.SEARCH_NUM = searchresults.SEARCH_NUM)
   end as 'total_per_period_cash_out'
   from SearchResults 

重要な部分は、何度も何度も呼び出される検索結果cteです。実行プランは次のようになります。

enter image description here

同じ結果セットを取得してそれを回避し、その重い実行計画を回避する方法はありますか?

1
giantLincecum

誤った仮定の下でコードを記述している可能性があります。CTEの結果は保持されます

そうではない 、そしてそれらを参照するたびに、構文が再実行されます。

ここに簡単な例があります:

CREATE TABLE #dummy
(
    id INT
);
INSERT #dummy ( id )
VALUES ( 1 );


WITH yourmom
AS ( SELECT d.id
     FROM   #dummy AS d )
SELECT ym.id
FROM   yourmom AS ym
JOIN   yourmom AS ym2
ON ym2.id = ym.id
JOIN   yourmom AS ym3
ON ym3.id = ym.id;

クエリプラン を見ると、ベーステーブルのスキャンが3回あります。最初のFROMと各JOINのスキャンです。合計で3つです。

これはいくつかの場所で問題を引き起こしています:

  1. transactions CTEへの参加
  2. transactions内のsearchResults
  3. COUNTからの最終選択内のすべてのsearchresultsサブクエリ

それが2番目の問題につながります。CASEFROMの日付を計算するために使用する2つのTO式は完全に non-SARGable です。

次の2つのオプションがあります。

  1. 最初のCTEの結果を#temp tableに貼り付けます

  2. 計算された列をベーステーブルに追加する

この場合、おそらく計算された列を選択します

ALTER TABLE dbo.parmeters_table
ADD FROM_DATE_SEARCHED AS CASE WHEN FROM_DATE IS NOT NULL THEN FROM_DATE
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NOT NULL
                                    AND TO_DATE IS NOT NULL THEN DATEADD(DAY, SEARCH_DAYS * -1, TO_DATE)
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NOT NULL
                                    AND TO_DATE IS NULL THEN DATEADD(DAY, SEARCH_DAYS * -1, GETDATE())
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NULL
                                    AND TO_DATE IS NOT NULL
                                    AND DURATION = 'Yearly' THEN DATEADD(YEAR, -1, TO_DATE)
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NULL
                                    AND TO_DATE IS NULL
                                    AND DURATION = 'Yearly' THEN DATEADD(YEAR, -1, GETDATE())
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NULL
                                    AND TO_DATE IS NOT NULL
                                    AND DURATION = 'Monthly' THEN DATEADD(MONTH, -1, TO_DATE)
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NULL
                                    AND TO_DATE IS NULL
                                    AND DURATION = 'Monthly' THEN DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - 1, 0)
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NULL
                                    AND TO_DATE IS NOT NULL
                                    AND DURATION = 'Weekly' THEN DATEADD(DAY, -7, TO_DATE)
                               WHEN FROM_DATE IS NULL
                                    AND SEARCH_DAYS IS NULL
                                    AND TO_DATE IS NULL
                                    AND DURATION = 'Weekly' THEN DATEADD(DAY, -7, GETDATE())
                               ELSE DATEADD(MONTH, -1, GETDATE())
                          END,
    TO_DATE_SEARCHED AS CASE WHEN TO_DATE IS NOT NULL THEN TO_DATE
                             WHEN TO_DATE IS NULL
                                  AND DURATION = 'Monthly' THEN DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE()) - 1, -1)
                             ELSE GETDATE()
                        END;

これらの列にインデックスを付けて、dbo.ixf_transactionへの結合をより効率的にし、すべてのデータを前処理してから結合する必要をなくすことができます。

そして、searchResultsの結果を永続化するために、一時テーブルを使用します。これにより、各CASE式を再実行する必要がなくなります。

お役に立てれば!

7
Erik Darling

同じ結果セットを取得してそれを回避し、その重い実行計画を回避する方法はありますか?

Cte searchresultからの結果を一時テーブルに永続化して、代わりにメインクエリで一時テーブルを使用してください

4
Mikael Eriksson

一言で言えば、あなたは一度にすべてをやりすぎているようです:o)

最後にある最後の大きなSELECTは、すべての並列CASE式で、問題の一部です。その1つのビューの下にすべてのものを作成し、最後にその大きなクエリを1つのJOINに対して(ON句の条件を使用して)分割するか、場合によっては5つの個別のクエリに一致させる必要がありますそれらの条件のそれぞれ。どちらにしても、本当に、場合によってはこれらの副選択を取り除きたいです(特に、何かを見逃していない限り、SUMのすべてのブランチで同じ列のCASEを実行しているように見えるため) )。

編集:わかりました、早すぎる話ですが、一部のサブ選択で異なる条件があるようですが、それでも、2つのJOINSに減らすことができます。 a.trx_date = searchresults.trx_date条件が必要なデータと不要なデータ用。この時点でデータをよく理解していると思いますので、私は自分の深みから離れて歩き続けるつもりはありません。

おそらくCASE CTEのtimeframesブランチのいくつかをCOALESCEに変えますが、それはスタイルや好みの問題です。

1
Will Crawford