ローリング合計の計算に下限を設定する必要があります。たとえば、
PKID NumValue GroupID
----------------------------
1 -1 1
2 -2 1
3 5 1
4 -7 1
5 1 2
私はを頂きたい:
PKID RollingSum GroupID
----------------------------- ## Explanation:
1 0 1 ## 0 - 1 < 0 => 0
2 0 1 ## 0 - 2 < 0 => 0
3 5 1 ## 0 + 5 > 0 => 5
4 0 1 ## 5 - 7 < 0 => 0
負の数を追加すると合計が負になる場合、制限がアクティブになり、結果がゼロに設定されます。その後の加算は、元のローリング合計ではなく、この調整された値に基づく必要があります。
期待される結果は加算を使用して達成する必要があります。4番目の数値が-7から-3に変更される場合、4番目の結果は0ではなく2になります
いくつかのローリング数ではなく単一の合計を提供できる場合は、それも許容されます。ストアドプロシージャを使用して非負の加算を実装できますが、それでは低レベルすぎます。
これの実際の問題は、注文をプラスの金額として記録し、キャンセルをマイナスとして記録することです。接続の問題により、お客様はcancel
ボタンを2回以上クリックする場合があり、その結果、複数の負の値が記録されます。収益を計算する場合、「ゼロ」が売上の境界である必要があります。
このビジネスアプリケーションは完全に愚かですが、それについて私ができることは何もありません。この質問については、DBAが使用できるソリューションのみを検討してください。
GroupID
あたり最大50行を期待しています。
これが私が思いついた再帰的なCTEの例です(これはうまくいくようです)。 IsはRow_Number()OVERを使用して、ギャップのないシーケンス番号を作成します。あなたのデータでそれがどれほどうまく機能するかはわかりませんが、それは試してみる価値があります。
--Set up demo data
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
drop table #temp
go
create table #temp (PKID int, NumValue int, GroupID int)
insert into #temp values
(1,-1,1), (3,-2,1), (5,5,1), (7,-3,1), (9,1,2)
--here is the real code
;
with RowNumberAddedToTemp as
(
SELECT
ROW_NUMBER() OVER(ORDER BY PKID ASC) AS rn,
* from #temp
)
,x
AS (
SELECT PKID --Anchor row
,NumValue
,RunningTotal = CASE
WHEN NumValue < 0 --if initial value less than zero, make zero
THEN 0
ELSE NumValue
END
,GroupID
,rn
FROM RowNumberAddedToTemp
WHERE rn = 1
UNION ALL
SELECT y.PKID
,y.NumValue
,CASE
WHEN x.GroupID <> y.groupid --did GroupId change?
THEN CASE
WHEN y.NumValue < 0 --if value is less than zero, make zero
THEN 0
ELSE y.numvalue --start new groupid totals
END
WHEN x.RunningTotal + y.NumValue < 0 --If adding the current row makes the total < 0, make zero
THEN 0
ELSE x.RunningTotal + y.NumValue --Add to the running total for the current groupid
END
,y.Groupid
,y.rn
FROM x
INNER JOIN RowNumberAddedToTemp AS y ON y.rn = x.rn + 1
)
SELECT PKID
,Numvalue
,RunningTotal
,GroupID
FROM x
ORDER BY PKID
OPTION (MAXRECURSION 10000);
以下は、Scott Hodginの answer に類似した再帰的なソリューションですが、インデックスをより効果的に利用できるはずです。データがどのように見えるかに応じて、パフォーマンスが向上します。 20000グループで100万行のデータをモックアップしました。それぞれに50行が関連付けられています。
_DROP TABLE IF EXISTS biz_application_problems;
CREATE TABLE dbo.biz_application_problems (
PKID BIGINT NOT NULL,
NumValue INT NOT NULL,
GroupID INT NOT NULL,
PRIMARY KEY (PKID)
);
INSERT INTO dbo.biz_application_problems WITH (TABLOCK)
SELECT TOP (1000000)
2 * ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, CAST(CRYPT_GEN_RANDOM(1) AS INT) % 22 - 11
, 1 + (-1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) / 50
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
CREATE INDEX gotta_go_fast ON dbo.biz_application_problems (GroupID, PKID)
INCLUDE (NumValue);
_
テーブルに追加した非クラスター化インデックスにより、CTEの再帰的な部分で、1回のインデックスシークで次の行を取得できます。また、PK
に応じたテーブル内のデータの順序は関係ありません。ここでのもう1つのトリックは、ROW_NUMBER()
を使用して効率的に次の行を取得することです。 CTEの再帰部分ではTOP
を使用できないため、これが必要です。これがクエリです:
_WITH rec_cte AS (
SELECT TOP 1
GroupID
, PKID
, CASE WHEN NumValue < 0 THEN 0 ELSE NumValue END RunningSum
, NumValue -- for validation purposes only
FROM dbo.biz_application_problems
ORDER BY GroupId, PKID
UNION ALL
SELECT t.GroupID
, t.PKID
, t.RunningSum
, t.NumValue
FROM
(
SELECT
b.GroupID
, b.PKID
, CASE WHEN ca.RunningSum < 0 THEN 0 ELSE ca.RunningSum END RunningSum
, b.NumValue
, ROW_NUMBER() OVER (ORDER BY b.GroupId, b.PKID) rn
FROM dbo.biz_application_problems b
INNER JOIN rec_cte r ON
(b.GroupID = r.GroupID AND b.PKID > r.PKID) OR b.GroupID > r.GroupID
CROSS APPLY (
SELECT CASE WHEN b.GroupID <> r.GroupID THEN 0 ELSE r.RunningSum END + b.NumValue
) ca (RunningSum)
) t
WHERE t.rn = 1
)
SELECT *
FROM rec_cte
OPTION (MAXRECURSION 0);
_
結果のサンプルは次のとおりです。
_╔═════════╦══════╦════════════╦══════════╗
║ GroupID ║ PKID ║ RunningSum ║ NumValue ║
╠═════════╬══════╬════════════╬══════════╣
║ 7 ║ 700 ║ 13 ║ -4 ║
║ 8 ║ 702 ║ 0 ║ -2 ║
║ 8 ║ 704 ║ 7 ║ 7 ║
║ 8 ║ 706 ║ 8 ║ 1 ║
║ 8 ║ 708 ║ 3 ║ -5 ║
║ 8 ║ 710 ║ 0 ║ -4 ║
║ 8 ║ 712 ║ 0 ║ -7 ║
║ 8 ║ 714 ║ 7 ║ 7 ║
║ 8 ║ 716 ║ 2 ║ -5 ║
║ 8 ║ 718 ║ 10 ║ 8 ║
║ 8 ║ 720 ║ 0 ║ -11 ║
║ 8 ║ 722 ║ 0 ║ -7 ║
╚═════════╩══════╩════════════╩══════════╝
_
私のマシンでは、コードは100万行を処理するのに約10秒かかります。反復ごとに1行だけを返すインデックスシークを確認できます。
実際のプランをアップロードしました ここ も同様です。