SQL Server-複数の累計


8

トランザクションを含むベーステーブルがあり、累計を含むテーブルを作成する必要があります。それらはアカウントごとである必要があり、さらに各トランザクションのいくつかの実行合計が(トランザクションタイプによって異なります)あり、その中にサブアカウントごとのいくつかの実行合計があります。

私のベーステーブルには次のフィールドがあります(多かれ少なかれ)。

AccountID  |  SubAccountID   |  TransactionType  |  TransactionAmount

Account / TransactionTypeごとに約4種類の実行合計があり、Account / SubAccount / TransactionTypeごとに実行合計が2つあり、それぞれ約10のサブアカウントを持つ約200万のアカウントがあり、約10Kのトランザクションを取得している毎分(最大負荷時)、どのように実行しますか?

これがSQLジョブを介して非同期で実行され、トランザクション自体の一部ではなく集計を作成することも必須です。

私はここでカーソルを使用してかなり立ち往生しています-時間がかかりすぎます。私は多かれ少なかれ同じことをしているアドバイス/記事に本当に感謝します。


1
会計の標準的なアプローチは、現在の合計をすでにテーブルに保持することです。すべてのトランザクションで、アカウントの古い値だけでなく新しい値も保存します。これは1つのSQL SELECTステートメントで実行できるため、ここでカーソルを使用してスタックすることはありません。
TomTom、2012年

3
SQL Server 2000を使用していますか、それともウィンドウ関数(ROW_NUMBER、RANKなど)を使用できないようにする他の制限がありますか?
ブライアン

1
現在の合計が別の物理テーブルに格納されている場合、会計システムで問題が発生しました。当社のベンダーのソフトウェアは、実際のバランステーブルを更新せずに実際のトランザクションを更新する可能性があり、その結果、運用バランスが狂ってしまいます。適切に設計されたシステムはこれを回避できますが、個別のテーブルアプローチを使用する場合は、精度がいかに重要であるかを注意して検討してください。
Ben Brocka

なぜこれが要件であり、何を達成しようとしているのですか?必要に応じて、リクエストに応じて(「現在の」)指定されたデータをトランザクションテーブルにクエリし、1日の終わりに行を移動/集約することができます(データウェアハウジング。SQLServerがユーティリティを提供していると確信しています)。
時計じかけのミューズ

私はSQL Server 2005に制限されています。最後の合計を常に正確にする必要はありませんが、実行されたすべてのアクションの現在の合計をすべて保持する必要があります-「履歴」テーブル。TomTom-元のテーブルではこれを保持しません-さまざまなトランザクションタイプの合計をいくつか実行する必要があり、それらは元のテーブルに属していません。これはSELECTでのみ実行できるとは思いません。カーソルまたはwhileループです。そうでなければ学びたいです。X-Zero-これは一種のデータウェアハウジング手順です。1日1回ではなく、約1分ごとに実行する必要があります。
AvnerSo 2012年

回答:


7

非同期は、実行中の合計が常に完全に正確である必要はないことを意味します。または、データ変更パターンは、1回限りの実行中の合計ビルドが次のロードまで有効で正確になるようなものです。とにかく、あなたはその部分を熟考したと思いますので、私は要点を説明しません。

高性能でサポートされているメソッドの主なオプションは、SQLCLR関数/プロシージャ、またはUPDATEHugo Kornelisのセットベースの反復メソッドに基づくメソッドです。SQLCLRメソッド(プロシージャに実装されていますが、かなり簡単に変換できます)は、ここにあります

私はHugoの方法をオンラインで見つけることはできませんでしたが、優れたMVP Deep Dives(第1巻)に詳しく説明されています。Hugoの方法を説明するためのサンプルコード(ログインしていない別のサイトの私の投稿の1つからコピーしたもの)を以下に示します。

-- A work table to hold the reformatted data, and
-- ultimately, the results
CREATE  TABLE #Work
    (
    Acct_No         VARCHAR(20) NOT NULL,
    MonthDate       DATETIME NOT NULL,
    MonthRate       DECIMAL(19,12) NOT NULL,
    Amount          DECIMAL(19,12) NOT NULL,
    InterestAmount  DECIMAL(19,12) NOT NULL,
    RunningTotal    DECIMAL(19,12) NOT NULL,
    RowRank         BIGINT NOT NULL
    );

-- Prepare the set-based iteration method
WITH    Accounts
AS      (
        -- Get a list of the account numbers
        SELECT  DISTINCT Acct_No 
        FROM    #Refunds
        ),
        Rates
AS      (
        -- Apply all the accounts to all the rates
        SELECT  A.Acct_No,
                R.[Year],
                R.[Month],
                MonthRate = R.InterestRate / 12
        FROM    #InterestRates R
        CROSS 
        JOIN    Accounts A
        ),
        BaseData
AS      (
        -- The basic data we need to work with
        SELECT  Acct_No = ISNULL(R.Acct_No,''),
                MonthDate = ISNULL(DATEADD(MONTH, R.[Month], DATEADD(YEAR, R.[year] - 1900, 0)), 0),
                R.MonthRate,
                Amount = ISNULL(RF.Amount,0),
                InterestAmount = ISNULL(RF.Amount,0) * R.MonthRate,
                RunningTotal = ISNULL(RF.Amount,0)
        FROM    Rates R
        LEFT
        JOIN    #Refunds RF
                ON  RF.Acct_No = R.Acct_No
                AND RF.[Year] = R.[Year]
                AND RF.[Month] = R.[Month]
        )
-- Basic data plus a rank id, numbering the rows by MonthDate, and resetting to 1 for each new Account
INSERT  #Work
        (Acct_No, MonthDate, MonthRate, Amount, InterestAmount, RunningTotal, RowRank)
SELECT  BD.Acct_No, BD.MonthDate, BD.MonthRate, BD.Amount, BD.InterestAmount, BD.RunningTotal,
        RowRank = RANK() OVER (PARTITION BY BD.Acct_No ORDER BY MonthDate)
FROM    BaseData BD;

-- An index to speed the next stage (different from that used with the Quirky Update method)
CREATE UNIQUE CLUSTERED INDEX nc1 ON #Work (RowRank, Acct_No);

-- Iteration variables
DECLARE @Rank       BIGINT,
        @RowCount   INTEGER;

-- Initialize
SELECT  @Rank = 1,
        @RowCount = 1;

-- This is the iteration bit, processes a rank id per iteration
-- The number of rows processed with each iteration is equal to the number of groups in the data
-- More groups --> greater efficiency
WHILE   (1 = 1)
BEGIN
        SET @Rank = @Rank + 1;

        -- Set-based update with running totals for the current rank id
        UPDATE  This
        SET     InterestAmount = (Previous.RunningTotal + This.Amount) * This.MonthRate,
                RunningTotal = Previous.RunningTotal + This.Amount + (Previous.RunningTotal + This.Amount) * This.MonthRate
        FROM    #Work This
        JOIN    #Work Previous
                ON  Previous.Acct_No = This.Acct_No
                AND Previous.RowRank = @Rank - 1
        WHERE   This.RowRank = @Rank;

        IF  (@@ROWCOUNT = 0) BREAK;
END;

-- Show the results in natural order
SELECT  *
FROM    #Work
ORDER   BY
        Acct_No, RowRank;

SQL Server 2012では、ウィンドウ関数拡張などを使用できますSUM OVER (ORDER BY)


5

非同期が必要な理由はわかりませんが、インデックス付きビューのいくつかは、ここでのチケットのように聞こえます。あるグループごとに単純なSUMが必要な場合:累計を定義します。

本当に非同期が必要な場合、毎秒160の新しい行があるため、現在の合計は常に古くなります。非同期はトリガーやインデックス付きビューがないことを意味します


5

カーソルを使用した場合でも、三角形の結合を使用した場合でも、積算合計の計算は非常に遅くなります。特に頻繁に選択する場合は、列に累計を保存するように非正規化するのは非常に魅力的です。ただし、通常どおりに非正規化する場合は、非正規化データの整合性を保証する必要があります。幸いにも、制約付きの積算合計の整合性を保証できます。すべての制約が信頼できる限り、積算合計はすべて正しいです。

また、この方法では、現在の残高(累計)が決してマイナスにならないことを簡単に確認できます。他の方法による強制も非常に遅くなる可能性があります。次のスクリプトは、この手法を示しています。

    CREATE TABLE Data.Inventory(InventoryID INT NOT NULL IDENTITY,
      ItemID INT NOT NULL,
      ChangeDate DATETIME NOT NULL,
      ChangeQty INT NOT NULL,
      TotalQty INT NOT NULL,
      PreviousChangeDate DATETIME NULL,
      PreviousTotalQty INT NULL,
      CONSTRAINT PK_Inventory PRIMARY KEY(ItemID, ChangeDate),
      CONSTRAINT UNQ_Inventory UNIQUE(ItemID, ChangeDate, TotalQty),
      CONSTRAINT UNQ_Inventory_Previous_Columns UNIQUE(ItemID, PreviousChangeDate, PreviousTotalQty),
      CONSTRAINT FK_Inventory_Self FOREIGN KEY(ItemID, PreviousChangeDate, PreviousTotalQty)
        REFERENCES Data.Inventory(ItemID, ChangeDate, TotalQty),
      CONSTRAINT CHK_Inventory_Valid_TotalQty CHECK(TotalQty >= 0 AND (TotalQty = COALESCE(PreviousTotalQty, 0) + ChangeQty)),
      CONSTRAINT CHK_Inventory_Valid_Dates_Sequence CHECK(PreviousChangeDate < ChangeDate),
      CONSTRAINT CHK_Inventory_Valid_Previous_Columns CHECK((PreviousChangeDate IS NULL AND PreviousTotalQty IS NULL)
                OR (PreviousChangeDate IS NOT NULL AND PreviousTotalQty IS NOT NULL))
    );
    GO
    -- beginning of inventory for item 1
    INSERT INTO Data.Inventory(ItemID,
      ChangeDate,
      ChangeQty,
      TotalQty,
      PreviousChangeDate,
      PreviousTotalQty)
    VALUES(1, '20090101', 10, 10, NULL, NULL);
    -- cannot begin the inventory for the second time for the same item 1
    INSERT INTO Data.Inventory(ItemID,
      ChangeDate,
      ChangeQty,
      TotalQty,
      PreviousChangeDate,
      PreviousTotalQty)
    VALUES(1, '20090102', 10, 10, NULL, NULL);


Msg 2627, Level 14, State 1, Line 10
Violation of UNIQUE KEY constraint 'UNQ_Inventory_Previous_Columns'. Cannot insert duplicate key in object 'Data.Inventory'.
The statement has been terminated.

-- add more
DECLARE @ChangeQty INT;
SET @ChangeQty = 5;
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
SELECT TOP 1 ItemID, '20090103', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = 3;
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
SELECT TOP 1 ItemID, '20090104', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = -4;
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
SELECT TOP 1 ItemID, '20090105', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

-- try to violate chronological order

SET @ChangeQty = 5;
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
SELECT TOP 1 ItemID, '20081231', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

Msg 547, Level 16, State 0, Line 4
The INSERT statement conflicted with the CHECK constraint "CHK_Inventory_Valid_Dates_Sequence". The conflict occurred in database "Test", table "Data.Inventory".
The statement has been terminated.


SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- -----
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 5           15          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           18          2009-01-03 00:00:00.000 15
2009-01-05 00:00:00.000 -4          14          2009-01-04 00:00:00.000 18


-- try to change a single row, all updates must fail
UPDATE Data.Inventory SET ChangeQty = ChangeQty + 2 WHERE InventoryID = 3;
UPDATE Data.Inventory SET TotalQty = TotalQty + 2 WHERE InventoryID = 3;
-- try to delete not the last row, all deletes must fail
DELETE FROM Data.Inventory WHERE InventoryID = 1;
DELETE FROM Data.Inventory WHERE InventoryID = 3;

-- the right way to update

DECLARE @IncreaseQty INT;
SET @IncreaseQty = 2;
UPDATE Data.Inventory SET ChangeQty = ChangeQty + CASE WHEN ItemID = 1 AND ChangeDate = '20090103' THEN @IncreaseQty ELSE 0 END,
  TotalQty = TotalQty + @IncreaseQty,
  PreviousTotalQty = PreviousTotalQty + CASE WHEN ItemID = 1 AND ChangeDate = '20090103' THEN 0 ELSE @IncreaseQty END
WHERE ItemID = 1 AND ChangeDate >= '20090103';

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- ----------------
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 7           17          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           20          2009-01-03 00:00:00.000 17
2009-01-05 00:00:00.000 -4          16          2009-01-04 00:00:00.000 20

私のブログからコピー

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.