次の表(TestTable
と呼ばれる)を想像してください。
id somedate somevalue
-- -------- ---------
45 01/Jan/09 3
23 08/Jan/09 5
12 02/Feb/09 0
77 14/Feb/09 7
39 20/Feb/09 34
33 02/Mar/09 6
次のように、日付順に実行合計を返すクエリが必要です。
id somedate somevalue runningtotal
-- -------- --------- ------------
45 01/Jan/09 3 3
23 08/Jan/09 5 8
12 02/Feb/09 0 8
77 14/Feb/09 7 15
39 20/Feb/09 34 49
33 02/Mar/09 6 55
SQL Server 2000/2005/2008には さまざまな方法 があります。
私は、集合セットステートメントのトリックを使用するこの種の方法に特に興味があります。
INSERT INTO @AnotherTbl(id, somedate, somevalue, runningtotal)
SELECT id, somedate, somevalue, null
FROM TestTable
ORDER BY somedate
DECLARE @RunningTotal int
SET @RunningTotal = 0
UPDATE @AnotherTbl
SET @RunningTotal = runningtotal = @RunningTotal + somevalue
FROM @AnotherTbl
...これは非常に効率的ですが、UPDATE
ステートメントが行を正しい順序で処理することを必ずしも保証できないため、これに関する問題があると聞きました。その問題について決定的な答えを得ることができるかもしれません。
しかし、人々が提案できる他の方法がありますか?
編集: SqlFiddle で、上記のセットアップと「更新のトリック」の例を使用して
Update、SQL Server 2012を実行している場合は、以下を参照してください: https://stackoverflow.com/a/10309947
問題は、Over句のSQL Server実装が ある程度制限されている であるということです。
Oracle(およびANSI-SQL)を使用すると、次のようなことができます。
SELECT somedate, somevalue,
SUM(somevalue) OVER(ORDER BY somedate
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS RunningTotal
FROM Table
SQL Serverには、この問題に対する明確な解決策はありません。私の直感では、これはカーソルが最速のまれなケースの1つであると私に言っていますが、大きな結果でベンチマークを行う必要があります。
更新のコツは便利ですが、かなり壊れやすいと感じています。テーブル全体を更新する場合は、主キーの順に処理されるようです。したがって、日付を昇順の主キーとして設定すると、probably
が安全になります。しかし、文書化されていないSQL Server実装の詳細に依存しています(また、クエリが2つのプロシージャによって実行されることになった場合、どうなるのか、MAXDOPを参照してください)。
完全な作業サンプル:
drop table #t
create table #t ( ord int primary key, total int, running_total int)
insert #t(ord,total) values (2,20)
-- notice the malicious re-ordering
insert #t(ord,total) values (1,10)
insert #t(ord,total) values (3,10)
insert #t(ord,total) values (4,1)
declare @total int
set @total = 0
update #t set running_total = @total, @total = @total + total
select * from #t
order by ord
ord total running_total
----------- ----------- -------------
1 10 10
2 20 30
3 10 40
4 1 41
ベンチマークをお願いしましたが、これはローダウンです。
これを行う最速の安全な方法はカーソルです。これは、相互結合の相関サブクエリよりも桁違いに高速です。
絶対的な最速の方法は、UPDATEトリックです。私の唯一の懸念は、すべての状況下で更新が線形に進むかどうか確信がないことです。明示的にそう言うクエリには何もありません。
一番下の行、量産コードの場合はカーソルを使用します。
テストデータ:
create table #t ( ord int primary key, total int, running_total int)
set nocount on
declare @i int
set @i = 0
begin tran
while @i < 10000
begin
insert #t (ord, total) values (@i, Rand() * 100)
set @i = @i +1
end
commit
テスト1:
SELECT ord,total,
(SELECT SUM(total)
FROM #t b
WHERE b.ord <= a.ord) AS b
FROM #t a
-- CPU 11731, Reads 154934, Duration 11135
テスト2:
SELECT a.ord, a.total, SUM(b.total) AS RunningTotal
FROM #t a CROSS JOIN #t b
WHERE (b.ord <= a.ord)
GROUP BY a.ord,a.total
ORDER BY a.ord
-- CPU 16053, Reads 154935, Duration 4647
テスト3:
DECLARE @TotalTable table(ord int primary key, total int, running_total int)
DECLARE forward_cursor CURSOR FAST_FORWARD
FOR
SELECT ord, total
FROM #t
ORDER BY ord
OPEN forward_cursor
DECLARE @running_total int,
@ord int,
@total int
SET @running_total = 0
FETCH NEXT FROM forward_cursor INTO @ord, @total
WHILE (@@FETCH_STATUS = 0)
BEGIN
SET @running_total = @running_total + @total
INSERT @TotalTable VALUES(@ord, @total, @running_total)
FETCH NEXT FROM forward_cursor INTO @ord, @total
END
CLOSE forward_cursor
DEALLOCATE forward_cursor
SELECT * FROM @TotalTable
-- CPU 359, Reads 30392, Duration 496
テスト4:
declare @total int
set @total = 0
update #t set running_total = @total, @total = @total + total
select * from #t
-- CPU 0, Reads 58, Duration 139
サム・サフロンはそれについてすばらしい仕事をしましたが、この問題に対して再帰共通テーブル式コードをまだ提供していませんでした。そして、DenaliではなくSQL Server 2008 R2を使用している私たちにとって、これはまだ合計を実行する最速の方法であり、10000行の作業コンピューター上のカーソルよりも約10倍高速であり、インラインクエリでもあります。
したがって、ここにあります(テーブルにord
列があり、ギャップのない連続番号であると仮定しています。高速処理のために、この番号にも一意の制約があるはずです)。
;with
CTE_RunningTotal
as
(
select T.ord, T.total, T.total as running_total
from #t as T
where T.ord = 0
union all
select T.ord, T.total, T.total + C.running_total as running_total
from CTE_RunningTotal as C
inner join #t as T on T.ord = C.ord + 1
)
select C.ord, C.total, C.running_total
from CTE_RunningTotal as C
option (maxrecursion 0)
-- CPU 140, Reads 110014, Duration 132
updateまた、このupdate with variableまたはquirky updateについても興味がありました。だから通常はうまくいくが、毎回うまくいくことをどのように確認できるのか?さて、ここにちょっとしたトリックがあります(ここにあります- http://www.sqlservercentral.com/Forums/Topic802558-203-21.aspx#bm981258 )-現在と以前のord
を確認して使用します予想と異なる場合の1/0
割り当て:
declare @total int, @ord int
select @total = 0, @ord = -1
update #t set
@total = @total + total,
@ord = case when ord <> @ord + 1 then 1/0 else ord end,
------------------------
running_total = @total
select * from #t
-- CPU 0, Reads 58, Duration 139
テーブルに適切なクラスター化インデックス/プライマリキーがある場合(私たちの場合、ord_id
によるインデックス)で見たものから、更新は常に線形に進みます(ゼロ除算に遭遇することはありません)。とはいえ、本番コードで使用するかどうかはあなた次第です:)
SQL 2005以降のAPPLY演算子は、これに対して機能します。
select
t.id ,
t.somedate ,
t.somevalue ,
rt.runningTotal
from TestTable t
cross apply (select sum(somevalue) as runningTotal
from TestTable
where somedate <= t.somedate
) as rt
order by t.somedate
SELECT TOP 25 amount,
(SELECT SUM(amount)
FROM time_detail b
WHERE b.time_detail_id <= a.time_detail_id) AS Total FROM time_detail a
ROW_NUMBER()関数と一時テーブルを使用して、内部SELECTステートメントでの比較に使用する任意の列を作成することもできます。
相関サブクエリを使用します。非常に簡単、ここに行きます:
SELECT
somedate,
(SELECT SUM(somevalue) FROM TestTable t2 WHERE t2.somedate<=t1.somedate) AS running_total
FROM TestTable t1
GROUP BY somedate
ORDER BY somedate
コードは正確に正確ではないかもしれませんが、アイデアは正しいと確信しています。
GROUP BYは、日付が複数回現れる場合に、結果セットで一度だけ見たいと思うでしょう。
繰り返しの日付を表示しても構わない場合、または元の値とIDを表示する場合は、次のようにします。
SELECT
id,
somedate,
somevalue,
(SELECT SUM(somevalue) FROM TestTable t2 WHERE t2.somedate<=t1.somedate) AS running_total
FROM TestTable t1
ORDER BY somedate
非正規化することもできます-同じテーブルに積算合計を保存します:
選択は他のソリューションよりもはるかに高速に動作しますが、変更はより遅くなる可能性があります
ウィンドウが他の場所(私が試した)と同じようにSQL Server 2008で機能すると仮定して、これを試してください:
select testtable.*, sum(somevalue) over(order by somedate)
from testtable
order by somedate;
MSDN は、SQL Server 2008(および2005も可能ですか?)で使用できると言っていますが、試してみるためのインスタンスがありません。
編集:まあ、どうやらSQL Serverは「PARTITION BY」を指定せずにウィンドウ指定(「OVER(...)」)を許可しないようです(結果をグループに分割しますが、GROUP BYのようにはまったく集約しません)。迷惑な-MSDN構文リファレンスではオプションであることが示唆されていますが、現時点ではSqlServer 2000インスタンスしかありません。
私が与えたクエリは、Oracle 10.2.0.3.0とPostgreSQL 8.4-betaの両方で機能します。 MSに追いつくように指示してください;)
上記のSQL Server 2008 R2を使用している場合。それから、それはする最短の方法です;
Select id
,somedate
,somevalue,
LAG(runningtotal) OVER (ORDER BY somedate) + somevalue AS runningtotal
From TestTable
LAG は、前の行の値を取得するために使用されます。 Googleで詳細を確認できます。
[1]:
最良の方法は、ウィンドウ関数を使用することですが、単純な相関サブクエリを使用して実行することもできます。
Select id, someday, somevalue, (select sum(somevalue)
from testtable as t2
where t2.id = t1.id
and t2.someday <= t1.someday) as runningtotal
from testtable as t1
order by id,someday;
結合の使用もう1つのバリエーションは、結合の使用です。これで、クエリは次のようになります。
SELECT a.id, a.value, SUM(b.Value)FROM RunTotalTestData a,
RunTotalTestData b
WHERE b.id <= a.id
GROUP BY a.id, a.value
ORDER BY a.id;
詳細については、このリンクをご覧ください http://askme.indianyouth.info/details/calculating-simple-running-totals-in-sql-server-12
以下の単純な内部結合操作を使用して、積算合計を達成できると思います。
SELECT
ROW_NUMBER() OVER (ORDER BY SomeDate) AS OrderID
,rt.*
INTO
#tmp
FROM
(
SELECT 45 AS ID, CAST('01-01-2009' AS DATETIME) AS SomeDate, 3 AS SomeValue
UNION ALL
SELECT 23, CAST('01-08-2009' AS DATETIME), 5
UNION ALL
SELECT 12, CAST('02-02-2009' AS DATETIME), 0
UNION ALL
SELECT 77, CAST('02-14-2009' AS DATETIME), 7
UNION ALL
SELECT 39, CAST('02-20-2009' AS DATETIME), 34
UNION ALL
SELECT 33, CAST('03-02-2009' AS DATETIME), 6
) rt
SELECT
t1.ID
,t1.SomeDate
,t1.SomeValue
,SUM(t2.SomeValue) AS RunningTotal
FROM
#tmp t1
JOIN #tmp t2
ON t2.OrderID <= t1.OrderID
GROUP BY
t1.OrderID
,t1.ID
,t1.SomeDate
,t1.SomeValue
ORDER BY
t1.OrderID
DROP TABLE #tmp
以下は、必要な結果を生成します。
SELECT a.SomeDate,
a.SomeValue,
SUM(b.SomeValue) AS RunningTotal
FROM TestTable a
CROSS JOIN TestTable b
WHERE (b.SomeDate <= a.SomeDate)
GROUP BY a.SomeDate,a.SomeValue
ORDER BY a.SomeDate,a.SomeValue
SomeDateにクラスター化インデックスを作成すると、パフォーマンスが大幅に向上します。
BEGIN TRAN
CREATE TABLE #Table (_Id INT IDENTITY(1,1) ,id INT , somedate VARCHAR(100) , somevalue INT)
INSERT INTO #Table ( id , somedate , somevalue )
SELECT 45 , '01/Jan/09', 3 UNION ALL
SELECT 23 , '08/Jan/09', 5 UNION ALL
SELECT 12 , '02/Feb/09', 0 UNION ALL
SELECT 77 , '14/Feb/09', 7 UNION ALL
SELECT 39 , '20/Feb/09', 34 UNION ALL
SELECT 33 , '02/Mar/09', 6
;WITH CTE ( _Id, id , _somedate , _somevalue ,_totvalue ) AS
(
SELECT _Id , id , somedate , somevalue ,somevalue
FROM #Table WHERE _id = 1
UNION ALL
SELECT #Table._Id , #Table.id , somedate , somevalue , somevalue + _totvalue
FROM #Table,CTE
WHERE #Table._id > 1 AND CTE._Id = ( #Table._id-1 )
)
SELECT * FROM CTE
ROLLBACK TRAN
積算合計を計算する2つの簡単な方法を次に示します。
アプローチ1:DBMSが分析関数をサポートしている場合、このように記述できます
SELECT id
,somedate
,somevalue
,runningtotal = SUM(somevalue) OVER (ORDER BY somedate ASC)
FROM TestTable
アプローチ2:データベースのバージョン/ DBMS自体が分析関数をサポートしていない場合、OUTER APPLYを利用できます
SELECT T.id
,T.somedate
,T.somevalue
,runningtotal = OA.runningtotal
FROM TestTable T
OUTER APPLY (
SELECT runningtotal = SUM(TI.somevalue)
FROM TestTable TI
WHERE TI.somedate <= S.somedate
) OA;
注:-異なるパーティションの実行合計を個別に計算する必要がある場合は、ここに投稿されているように実行できます: 行全体の実行合計の計算とIDによるグループ化