2

複数代入変数ソリューションを使用して、特定の列で分割された大規模な SQL Server 2008 データ セットで累積合計をすばやく作成する方法を探しています。非常に基本的な例として、以下の「cumulative_total」列を作成したいと思います。

user_id | month | total | cumulative_total

1       | 1     | 2.0   | 2.0
1       | 2     | 1.0   | 3.0
1       | 3     | 3.5   | 8.5

2       | 1     | 0.5   | 0.5
2       | 2     | 1.5   | 2.0
2       | 3     | 2.0   | 4.0

従来、相関サブクエリを使用してこれを行ってきましたが、大量のデータ (200,000 行以上、現在の合計のいくつかの異なるカテゴリ) では、理想的なパフォーマンスが得られません。

私は最近、ここで累積合計に複数の割り当て変数を使用することについて読みました:

http://sqlblog.com/blogs/paul_nielsen/archive/2007/12/06/cumulative-totals-screencast.aspx

そのブログの例では、累積変数ソリューションは次のようになります。

UPDATE my_table
SET @CumulativeTotal=cumulative_total=@CumulativeTotal+ISNULL(total, 0)

このソリューションは、上記の例の 1 人のユーザー (ユーザー 1 またはユーザー 2) の合計を非常に高速に処理しているように見えます。ただし、ユーザーごとに効果的に分割する必要があります。月ごとのユーザーごとの累積合計を教えてください。

これを解決するために複数の割り当て変数の概念を拡張する方法、または相関サブクエリまたはカーソル以外の他のアイデアを知っている人はいますか?

ヒントをありがとう。

4

2 に答える 2

6

データを保存する必要がない場合 (行が変更、追加、または削除されるたびに実行中の合計を更新する必要があるため、そうすべきではありません)、および風変わりな更新を信頼しない場合 (これは、動作が保証されておらず、ホットフィックス、サービス パック、アップグレード、または基になるインデックスや統計の変更によって動作が変わる可能性があるため、実行時にこのタイプのクエリを試すことができます。これは、MVP の仲間である Hugo Kornelis が「セットベースの反復」を作成したメソッドです (彼は、SQL Server MVP Deep Divesの章の 1 つに同様のことを投稿しました)。)。通常、合計を実行するには、セット全体に対するカーソル、セット全体に対する風変わりな更新、または行数が増えるにつれてますますコストがかかる単一の非線形自己結合が必要になるため、ここでのトリックは、いくつかの有限をループすることです。セット内の要素 (この場合、各ユーザーの月単位の各行の「ランク」。そのランクのすべてのユーザー/月の組み合わせに対して各ランクのみを 1 回処理するため、200,000 行をループする代わりに、最大 24 回ループします)。

DECLARE @t TABLE
(
  [user_id] INT, 
  [month] TINYINT,
  total DECIMAL(10,1), 
  RunningTotal DECIMAL(10,1), 
  Rnk INT
);

INSERT @t SELECT [user_id], [month], total, total, 
  RANK() OVER (PARTITION BY [user_id] ORDER BY [month]) 
  FROM dbo.my_table;

DECLARE @rnk INT = 1, @rc INT = 1;

WHILE @rc > 0
BEGIN
  SET @rnk += 1;

  UPDATE c SET RunningTotal = p.RunningTotal + c.total
    FROM @t AS c INNER JOIN @t AS p
    ON c.[user_id] = p.[user_id]
    AND p.rnk = @rnk - 1
    AND c.rnk = @rnk;

  SET @rc = @@ROWCOUNT;
END

SELECT [user_id], [month], total, RunningTotal
FROM @t
ORDER BY [user_id], rnk;

結果:

user_id  month   total   RunningTotal
-------  -----   -----   ------------
1        1       2.0     2.0
1        2       1.0     3.0
1        3       3.5     6.5 -- I think your calculation is off
2        1       0.5     0.5
2        2       1.5     2.0
2        3       2.0     4.0

もちろん、このテーブル変数からベース テーブルを更新することはできますが、それらの保存された値は、次にテーブルが DML ステートメントによって処理されるまでしか有効ではないため、わざわざする必要はありません。

UPDATE mt
  SET cumulative_total = t.RunningTotal
  FROM dbo.my_table AS mt
  INNER JOIN @t AS t
  ON mt.[user_id] = t.[user_id]
  AND mt.[month] = t.[month];

いかなる種類の暗黙的な順序付けにも依存していないため、これは 100% サポートされており、サポートされていない風変わりな更新と比較してパフォーマンスを比較する価値があります。たとえそれが勝てなくても、私見でそれを使用することを検討する必要があります。

SQL Server 2012 ソリューションについては、Matt が言及していますRANGEが、この方法ではディスク上のスプールを使用するためROWS、単に実行するのではなく でテストする必要がありますRANGE。あなたの場合の簡単な例を次に示します。

SELECT
  [user_id],
  [month],
  total,
  RunningTotal = SUM(total) OVER 
  (
    PARTITION BY [user_id] 
    ORDER BY [month] ROWS UNBOUNDED PRECEDING
  )
FROM dbo.my_table
ORDER BY [user_id], [month];

RANGE UNBOUNDED PRECEDINGこれを使用するか使用しないかを比較します (ディスク上のスプールROWS\RANGEも使用します)。RANGE上記は、プランが少し複雑に見えますが (追加のシーケンス プロジェクト オペレーター)、全体の期間が短くなり、I/O が大幅に少なくなります

私は最近、特定の実行中の合計シナリオで観察したいくつかのパフォーマンスの違いを概説するブログ投稿を公開しました。

http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

于 2012-05-31T12:23:22.570 に答える
2

SQL Server 2008 でのオプションはかなり制限されています。つまり、上記の方法に基づいて何かを実行したり (これは「風変わりな更新」と呼ばれます)、CLR で何かを実行したりできます。

個人的には、動作が保証されている CLR を使用しますが、風変わりな更新構文は正式にサポートされていません (そのため、将来のバージョンでは機能しなくなる可能性があります)。

探している風変わりな更新構文のバリエーションは次のようになります。

UPDATE my_table
SET @CumulativeTotal=cumulative_total=ISNULL(total, 0) + 
        CASE WHEN @user=@lastUser THEN @CumulativeTotal ELSE 0 END, 
    @user=lastUser

SQL Server 2012 ではRANGEウィンドウ関数のサポートが導入されているため、これは 100% サポートされながら、最も効率的な方法で表現できることに注意してください。

于 2012-05-31T11:01:14.420 に答える