sql >> データベース >  >> RDS >> Sqlserver

ランニングトータル/ランニングバランスを計算する

    SQL Server 2012以降を使用していない場合は、カーソルが最も効率的なサポートである可能性があります。 および保証 CLR外のメソッド。他にも、わずかに高速であるが将来の動作が保証されない「風変わりな更新」などのアプローチがあります。もちろん、テーブルが大きくなるにつれて双曲線パフォーマンスプロファイルを使用するセットベースのアプローチや、直接を必要とすることが多い再帰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;
    

    再帰的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;
    

    SQLServer2012以降

    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

    パーティション化すると、現在の合計クエリが生成されます

    GroupByを使用した複数の累計



    1. SQL Server Management Studio(SSMS)で行番号を追加する方法-SQL Server/TSQLチュートリアルパート11

    2. ブールパラメータをOracleプロシージャC#に渡す方法

    3. 配列引数を使用して集合を返す関数を複数回呼び出す

    4. psqlクライアントは今チェス盤になることができます…