以前にも似たようなものを投稿しましたが、今は違う方向からアプローチしているので、新しい質問をしました。これで大丈夫だといいのですが。
私は、親の請求に基づいて請求の合計を作成するCTEを使用しています。 SQLと詳細はここで見ることができます:
CTEに何も欠けているとは思いませんが、大きなデータテーブル(350万行)で使用すると問題が発生します。
テーブルtblChargeShare
には、InvoiceID
など、必要なその他の情報が含まれているため、CTEをビューvwChargeShareSubCharges
に配置し、テーブルに結合しました。
クエリ:
Select t.* from vwChargeShareSubCharges t
inner join
tblChargeShare s
on t.CustomerID = s.CustomerID
and t.MasterChargeID = s.ChargeID
Where s.ChargeID = 1291094
数ミリ秒で結果を返します。
クエリ:
Select ChargeID from tblChargeShare Where InvoiceID = 1045854
1行を返します:
1291094
しかし、クエリ:
Select t.* from vwChargeShareSubCharges t
inner join
tblChargeShare s
on t.CustomerID = s.CustomerID
and t.MasterChargeID = s.ChargeID
Where InvoiceID = 1045854
実行には2〜3分かかります。
実行プランを保存し、SQLSentryにロードしました。高速クエリのツリーは次のようになります。
遅いクエリからの計画は次のとおりです。
インデックスの再作成、チューニングアドバイザーを介したクエリの実行、およびサブクエリのさまざまな組み合わせを試しました。結合にPK以外のものが含まれている場合は常に、クエリは遅くなります。
私はここで同様の質問をしました:
SQL ServerクエリはWhere句に応じてタイムアウトします
CTEの代わりに子行の合計を行うために関数を使用したもの。これは、現在発生しているのと同じ問題を回避するためにCTEを使用して書き直したものです。私はその回答の回答を読みましたが、私は賢明ではありません-ヒントとパラメーターに関するいくつかの情報を読みましたが、それを機能させることができません。 CTEを使って書き直すことで問題が解決すると思っていました。数千行のtblChargeで実行すると、クエリは高速になります。
SQL 2008R2とSQL2012の両方でテスト済み
編集:
クエリを1つのステートメントに凝縮しましたが、同じ問題が解決しません。
WITH RCTE AS
(
SELECT ParentChargeId, s.ChargeID, 1 AS Lvl, ISNULL(TotalAmount, 0) as TotalAmount, ISNULL(s.TaxAmount, 0) as TaxAmount,
ISNULL(s.DiscountAmount, 0) as DiscountAmount, s.CustomerID, c.ChargeID as MasterChargeID
from tblCharge c inner join tblChargeShare s
on c.ChargeID = s.ChargeID Where s.ChargeShareStatusID < 3 and ParentChargeID is NULL
UNION ALL
SELECT c.ParentChargeID, c.ChargeID, Lvl+1 AS Lvl, ISNULL(s.TotalAmount, 0), ISNULL(s.TaxAmount, 0), ISNULL(s.DiscountAmount, 0) , s.CustomerID
, rc.MasterChargeID
from tblCharge c inner join tblChargeShare s
on c.ChargeID = s.ChargeID
INNER JOIN RCTE rc ON c.PArentChargeID = rc.ChargeID and s.CustomerID = rc.CustomerID Where s.ChargeShareStatusID < 3
)
Select MasterChargeID as ChargeID, rcte.CustomerID, Sum(rcte.TotalAmount) as TotalCharged, Sum(rcte.TaxAmount) as TotalTax, Sum(rcte.DiscountAmount) as TotalDiscount
from RCTE inner join tblChargeShare s on rcte.ChargeID = s.ChargeID and RCTE.CustomerID = s.CustomerID
Where InvoiceID = 1045854
Group by MasterChargeID, rcte.CustomerID
GO
編集:もっと遊んで、私はこれを理解していません。
このクエリは瞬時(2ms)です:
Select t.* from
vwChargeShareSubCharges t
Where t.MasterChargeID = 1291094
これには3分かかりますが:
DECLARE @ChargeID int = 1291094
Select t.* from
vwChargeShareSubCharges t
Where t.MasterChargeID = @ChargeID
「In」に数字のヒープを入れても、クエリはすぐに実行されます。
Where t.MasterChargeID in (1291090, 1291091, 1291092, 1291093, 1291094, 1291095, 1291096, 1291097, 1291098, 1291099, 129109)
編集2:
このサンプルデータを使用して、これを最初から複製できます。
問題を再現するために、いくつかのダミーデータを作成しました。 100,000行しか追加しなかったので、それほど重要ではありませんが、実行プランが正しくありません(SQLCMDモードで実行)。
CREATE TABLE [tblChargeTest](
[ChargeID] [int] IDENTITY(1,1) NOT NULL,
[ParentChargeID] [int] NULL,
[TotalAmount] [money] NULL,
[TaxAmount] [money] NULL,
[DiscountAmount] [money] NULL,
[InvoiceID] [int] NULL,
CONSTRAINT [PK_tblChargeTest] PRIMARY KEY CLUSTERED
(
[ChargeID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
END
GO
Insert into tblChargeTest
(discountAmount, TotalAmount, TaxAmount)
Select ABS(CHECKSUM(NewId())) % 10, ABS(CHECKSUM(NewId())) % 100, ABS(CHECKSUM(NewId())) % 10
GO 100000
Update tblChargeTest
Set ParentChargeID = (ABS(CHECKSUM(NewId())) % 60000) + 20000
Where ChargeID = (ABS(CHECKSUM(NewId())) % 20000)
GO 5000
CREATE VIEW [vwChargeShareSubCharges] AS
WITH RCTE AS
(
SELECT ParentChargeId, ChargeID, 1 AS Lvl, ISNULL(TotalAmount, 0) as TotalAmount, ISNULL(TaxAmount, 0) as TaxAmount,
ISNULL(DiscountAmount, 0) as DiscountAmount, ChargeID as MasterChargeID
FROM tblChargeTest Where ParentChargeID is NULL
UNION ALL
SELECT rh.ParentChargeID, rh.ChargeID, Lvl+1 AS Lvl, ISNULL(rh.TotalAmount, 0), ISNULL(rh.TaxAmount, 0), ISNULL(rh.DiscountAmount, 0)
, rc.MasterChargeID
FROM tblChargeTest rh
INNER JOIN RCTE rc ON rh.PArentChargeID = rc.ChargeID --and rh.CustomerID = rc.CustomerID
)
Select MasterChargeID, ParentChargeID, ChargeID, TotalAmount, TaxAmount, DiscountAmount , Lvl
FROM RCTE r
GO
次に、次の2つのクエリを実行します。
--Slow Query:
Declare @ChargeID int = 60900
Select *
from [vwChargeShareSubCharges]
Where MasterChargeID = @ChargeID
--Fast Query:
Select *
from [vwChargeShareSubCharges]
Where MasterChargeID = 60900
ここでSQLServerが実行できる最善の方法は、ChargeID
のフィルターをビュー内の再帰CTEのアンカー部分にプッシュすることです。これにより、シークは階層を構築するために必要な唯一の行を見つけることができます。パラメータを定数値として指定すると、SQL Serverはその最適化を行うことができます(そのようなことに興味がある人のために、SelOnIterator
と呼ばれるルールを使用して):
ローカル変数を使用する場合、これを行うことはできないため、ChargeID
の述語はビューの外でスタックします(これにより、すべてのNULL
IDから始まる完全な階層が構築されます):
変数を使用するときに最適なプランを取得する1つの方法は、実行のたびにオプティマイザーに新しいプランをコンパイルさせることです。結果のプランは、実行時に変数の特定の値に合わせて調整されます。これは、OPTION (RECOMPILE)
クエリヒントを追加することで実現されます。
Declare @ChargeID int = 60900;
-- Produces a fast execution plan, at the cost of a compile on every execution
Select *
from [vwChargeShareSubCharges]
Where MasterChargeID = @ChargeID
OPTION (RECOMPILE);
2番目のオプションは、ビューをインラインテーブル関数に変更することです。これにより、フィルタリング述語の位置を明示的に指定できます。
CREATE FUNCTION [dbo].[udfChargeShareSubCharges]
(
@ChargeID int
)
RETURNS TABLE AS RETURN
(
WITH RCTE AS
(
SELECT ParentChargeID, ChargeID, 1 AS Lvl, ISNULL(TotalAmount, 0) as TotalAmount, ISNULL(TaxAmount, 0) as TaxAmount,
ISNULL(DiscountAmount, 0) as DiscountAmount, ChargeID as MasterChargeID
FROM tblChargeTest
Where ParentChargeID is NULL
AND ChargeID = @ChargeID -- Filter placed here explicitly
UNION ALL
SELECT rh.ParentChargeID, rh.ChargeID, Lvl+1 AS Lvl, ISNULL(rh.TotalAmount, 0), ISNULL(rh.TaxAmount, 0), ISNULL(rh.DiscountAmount, 0)
, rc.MasterChargeID
FROM tblChargeTest rh
INNER JOIN RCTE rc ON rh.ParentChargeID = rc.ChargeID --and rh.CustomerID = rc.CustomerID
)
Select MasterChargeID, ParentChargeID, ChargeID, TotalAmount, TaxAmount, DiscountAmount , Lvl
FROM RCTE r
)
次のように使用します。
Declare @ChargeID int = 60900
select *
from dbo.udfChargeShareSubCharges(@ChargeID)
クエリは、ParentChargeID
のインデックスからも恩恵を受けることができます。
create index ix_ParentChargeID on tblChargeTest(ParentChargeID)
同様のシナリオでの同様の最適化ルールに関する別の回答があります。 ウィンドウ関数を含むパラメーター化されたT-SQLクエリの実行プランの最適化
次に解決策を見つけるために、CTEをe tempテーブルに選択し、そこから結合することをお勧めします。 CTEに参加した個人的な経験から、CTEによって生成されたデータを一時テーブルに挿入するだけでクエリが5分間返され、わずか4秒に短縮されました。私は実際に2つのCTEを結合していましたが、これはCTEがLONGテーブルに結合されている場合(特に外部結合)のすべての長時間実行クエリに当てはまると思います。
--temp tables if needed to work with intermediate values
If object_id('tempdb..#p') is not null
drop table #p
;WITH cte as (
select * from t1
)
select *
into #p
from cte
--then use the temp table as you would normally use the CTE
select * from #p