私はテーブルを持っています:
create table Transactions(Tid int,amt int)
5行の場合:
insert into Transactions values(1, 100)
insert into Transactions values(2, -50)
insert into Transactions values(3, 100)
insert into Transactions values(4, -100)
insert into Transactions values(5, 200)
望ましい出力:
TID amt balance
--- ----- -------
1 100 100
2 -50 50
3 100 150
4 -100 50
5 200 250
基本的に最初のレコードのバランスはamt
と同じになり、2番目以降のバランスは前のバランス+現在のamt
の加算になります。最適なアプローチを探しています。関数または相関サブクエリの使用について考えることはできましたが、それを正確に行う方法はわかりません。
SQL Server 2012以降を使用していない場合は、カーソルがおそらく最も効率的supportedおよびguaranteedCLRの外部のメソッド。わずかに高速であるが将来の動作が保証されない「風変わりな更新」や、もちろん、テーブルが大きくなるにつれて双曲線パフォーマンスプロファイルを使用するセットベースのアプローチや、多くの場合直接アクセスを必要とする再帰CTEメソッドなどのアプローチがあります#tempdb I/O、またはほぼ同じ影響をもたらす流出が発生します。
遅いセットベースのアプローチは次の形式です。
_SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;
_
これが遅い理由は?テーブルが大きくなると、各増分行はテーブル内のn-1行を読み取る必要があります。これは指数関数的であり、失敗、タイムアウト、または単に怒っているユーザーに限定されます。
副照会形式も、同様に痛みを伴う理由で同様に痛みを伴います。
_SELECT TID, amt, RunningTotal = amt + COALESCE(
(
SELECT SUM(amt)
FROM dbo.Transactions AS i
WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;
_
「風変わりな更新」メソッドは上記よりも効率的ですが、動作は文書化されておらず、順序についての保証もありません。また、動作は今日は動作する可能性がありますが、将来は壊れる可能性があります。これは一般的な方法であり、効率的であるため、これを含めていますが、それは私がそれを支持するという意味ではありません。重複として閉じるのではなくこの質問に答えた主な理由は、 他の質問には受け入れられた答えとして風変わりな更新がある であるためです。
_DECLARE @t TABLE
(
TID INT PRIMARY KEY,
amt INT,
RunningTotal INT
);
DECLARE @RunningTotal INT = 0;
INSERT @t(TID, amt, RunningTotal)
SELECT TID, amt, RunningTotal = 0
FROM dbo.Transactions
ORDER BY TID;
UPDATE @t
SET @RunningTotal = RunningTotal = @RunningTotal + amt
FROM @t;
SELECT TID, amt, RunningTotal
FROM @t
ORDER BY TID;
_
この最初の例は、TIDが連続していてギャップがないことを前提としています。
_;WITH x AS
(
SELECT TID, amt, RunningTotal = amt
FROM dbo.Transactions
WHERE TID = 1
UNION ALL
SELECT y.TID, y.amt, x.RunningTotal + y.amt
FROM x
INNER JOIN dbo.Transactions AS y
ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
_
これに頼ることができない場合は、このバリエーションを使用できます。これは、単にROW_NUMBER()
を使用して連続したシーケンスを構築します。
_;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
FROM dbo.Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
_
データのサイズ(たとえば、不明な列)によっては、最初に#tempテーブルにのみ関連する列を詰め込み、ベーステーブルではなくその列に対して処理することで、全体的なパフォーマンスが向上する場合があります。
_CREATE TABLE #x
(
rn INT PRIMARY KEY,
TID INT,
amt INT
);
INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
TID, amt
FROM dbo.Transactions;
;WITH x AS
(
SELECT TID, rn, amt, rt = amt
FROM #x
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN #x AS y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
DROP TABLE #x;
_
最初のCTEメソッドのみが、風変わりな更新に匹敵するパフォーマンスを提供しますが、データの性質(ギャップなし)について大きな仮定を行います。他の2つの方法はフォールバックし、その場合はカーソルを使用することもできます(CLRを使用できず、SQL Server 2012以降をまだ使用していない場合)。
カーソルは悪であり、すべての犠牲を払って避けるべきであるとみんなに言われますが、これは実際にサポートされている他のほとんどの方法のパフォーマンスに勝っており、風変わりな更新より安全です。カーソルソリューションよりも私が好む唯一のものは、2012およびCLRメソッド(下記)です。
_CREATE TABLE #x
(
TID INT PRIMARY KEY,
amt INT,
rt INT
);
INSERT #x(TID, amt)
SELECT TID, amt
FROM dbo.Transactions
ORDER BY TID;
DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;
DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT TID, amt FROM #x ORDER BY TID;
OPEN c;
FETCH c INTO @tid, @amt;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @rt = @rt + @amt;
UPDATE #x SET rt = @rt WHERE TID = @tid;
FETCH c INTO @tid, @amt;
END
CLOSE c; DEALLOCATE c;
SELECT TID, amt, RunningTotal = rt
FROM #x
ORDER BY TID;
DROP TABLE #x;
_
SQL Server 2012で導入された新しいウィンドウ関数により、このタスクが非常に簡単になります(また、上記のすべての方法よりもパフォーマンスが向上します)。
_SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
_
RANGEはオンディスクスプールを使用する(およびデフォルトではRANGEを使用する)ため、より大きなデータセットでは、上記の方が次の2つのオプションのいずれよりもはるかに優れていることがわかります。ただし、動作と結果は異なる可能性があることに注意することも重要です。そのため、この違いに基づいて決定する前に、両方が正しい結果を返すことを確認してください。
_SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
_
完全を期すために、私はPavel PawlowskiのCLRメソッドへのリンクを提供します。これは、SQL Server 2012より前のバージョンで明らかに望ましい方法ですが(明らかに2000ではありません)。
http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/
SQL Server 2012以降を使用している場合、選択は明らかです。新しいSUM() OVER()
コンストラクト(ROWS
vs. RANGE
を使用)を使用してください。以前のバージョンでは、スキーマ、データの代替アプローチのパフォーマンスを比較し、パフォーマンスに関連しない要因を考慮して、どのアプローチが適切かを判断する必要があります。それは非常によくCLRアプローチかもしれません。私の推奨事項を優先順に示します。
SUM() OVER() ... ROWS
、2012以上の場合これらのメソッドのパフォーマンス比較の詳細については、 http://dba.stackexchange.com でこの質問を参照してください。
https://dba.stackexchange.com/questions/19507/running-total-with-count
これらの比較の詳細については、こちらにもブログで紹介しています。
http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
グループ化/パーティション化された積算合計については、次の投稿を参照してください。
http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals
バージョン2012を使用している場合、ここに解決策があります
select *, sum(amt) over (order by Tid) as running_total from Transactions
以前のバージョン
select *,(select sum(amt) from Transactions where Tid<=t.Tid) as running_total from Transactions as t
2008R2を使用しており、変数と一時テーブルを使用しています。これにより、caseステートメントを使用して各行を計算するときにカスタム処理を実行することもできます(つまり、特定のトランザクションは異なる動作をするか、特定のトランザクションタイプの合計のみが必要になる場合があります)
DECLARE @RunningBalance int = 0
SELECT Tid, Amt, 0 AS RunningBalance
INTO #TxnTable
FROM Transactions
ORDER BY Tid
UPDATE #TxnTable
SET @RunningBalance = RunningBalance = @RunningBalance + Amt
SELECT * FROM #TxnTable
DROP TABLE #TxnTable
3,300を超えるトランザクションを持つアイテムを持つ230万行のトランザクションテーブルがあり、このタイプのクエリを実行するのにまったく時間はかかりません。
select v.ID
,CONVERT(VARCHAR(10), v.EntryDate, 103) + ' ' + convert(VARCHAR(8), v.EntryDate, 14)
as EntryDate
,case
when v.CreditAmount<0
then
ISNULL(v.CreditAmount,0)
else
0
End as credit
,case
when v.CreditAmount>0
then
v.CreditAmount
else
0
End as debit
,Balance = SUM(v.CreditAmount) OVER (ORDER BY v.ID ROWS UNBOUNDED PRECEDING)
from VendorCredit v
order by v.EntryDate desc
SQL Server 2008+で
SELECT T1.* ,
T2.RunningSum
FROM dbo.Transactions As T1
CROSS APPLY ( SELECT SUM(amt) AS RunningSum
FROM dbo.Transactions AS CAT1
WHERE ( CAT1.TId <= T1.TId )
) AS T2
SQL Server 2012以降
SELECT * ,
SUM(T1.amt) OVER ( ORDER BY T1.TId
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS RunningTotal
FROM dbo.Transactions AS t1