58

私はテーブルを持っています:

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。最適なアプローチを探しています。関数または相関サブクエリの使用について考えることができましたが、それを行う方法が正確にはわかりません。

4

6 に答える 6

165

SQL Server 2012 以降を使用していない場合、カーソルは、CLR 以外でサポートおよび保証されている最も効率的な方法である可能性があります。わずかに高速になる可能性がありますが、将来的に機能することが保証されていない「風変わりな更新」などの他のアプローチがあります。もちろん、テーブルが大きくなるにつれて双曲線パフォーマンスプロファイルを使用するセットベースのアプローチや、しばしば直接を必要とする再帰的な CTE メソッドがあります。 #tempdb I/O またはスピルが発生し、ほぼ同じ影響が生じます。


INNER JOIN - これを行わないでください:

遅いセットベースのアプローチは、次の形式です。

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;

再帰的 CTE

この最初のものは、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 以降

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;

CLR

完全を期すために、私は 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アプローチである可能性が非常に高いです。以下に、私の推奨事項を優先順に示します。

  1. SUM() OVER() ... ROWS、2012年以降の場合
  2. 可能であれば、CLR メソッド
  3. 可能であれば、最初の再帰 CTE メソッド
  4. カーソル
  5. その他の再帰的 CTE メソッド
  6. 風変わりな更新
  7. 結合および/または相関サブクエリ

これらの方法のパフォーマンス比較の詳細については、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

パーティショニングの結果は実行中の合計クエリになります

Group By を使用した複数の累計

于 2012-07-03T14:51:57.730 に答える
6

バージョン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
于 2012-07-03T12:32:57.490 に答える
1

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 サーバー 2012+

SELECT  * ,
        SUM(T1.amt) OVER ( ORDER BY T1.TId 
                        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS RunningTotal
FROM    dbo.Transactions AS t1
于 2016-10-20T20:54:32.953 に答える
0

2012 のSUMandOVER関数を使用すると、 and をネストできるようsumになりcountsました。

SELECT date, sum(count(DISTINCT unique_id)) OVER (ORDER BY date) AS total_per_date
FROM dbo.table
GROUP BY date
于 2019-09-16T01:58:16.627 に答える