SQL Server - Nhiều tổng số đang chạy


8

Tôi đã có một bảng cơ sở với các giao dịch và tôi cần tạo một bảng có tổng số đang chạy. Tôi cần chúng cho mỗi tài khoản và cũng có một vài tổng số đang chạy cho mỗi tài khoản (tùy thuộc vào loại giao dịch) và bên trong đó, một số tổng số đang chạy cho mỗi tài khoản phụ.

Bảng cơ sở của tôi có các trường này (nhiều hơn hoặc ít hơn):

AccountID  |  SubAccountID   |  TransactionType  |  TransactionAmount

Xem xét tôi đã có khoảng 4 loại tổng số đang chạy cho mỗi Tài khoản / Loại giao dịch và 2 loại tổng số đang chạy khác cho mỗi Tài khoản / SubAccount / Giao dịch và tôi có khoảng 2 triệu tài khoản với khoảng 10 tài khoản phụ và tôi nhận được khoảng 10 nghìn giao dịch mỗi phút (ở mức tải tối đa), bạn sẽ làm thế nào?

Điều này cũng bắt buộc là điều này chạy không đồng bộ thông qua một công việc SQL, tạo ra các tập hợp mà không phải là một phần của chính các giao dịch.

Tôi khá bế tắc khi sử dụng một con trỏ ở đây - quá lâu. Tôi thực sự đánh giá cao bất kỳ lời khuyên / bài viết nào đang làm ít nhiều giống nhau.


1
Phương pháp chuẩn mực kế toán là giữ cho tổng số đã chạy trong một bảng. Tôi lưu trữ với mọi giao dịch không chỉ giá trị cũ mà cả giá trị mới của tài khoản. Bạn không bị mắc kẹt khi sử dụng một con trỏ ở đây, vì điều này có thể được thực hiện trong một câu lệnh CHỌN sql.
TomTom

3
Bạn có trên SQL Server 2000 không, hoặc có những hạn chế nào khác ngăn bạn sử dụng các chức năng của cửa sổ (ROW_NUMBER, RANK, v.v.) không?
Bryan

1
Hệ thống kế toán của chúng tôi đã có vấn đề khi chạy tổng số được lưu trữ trong một bảng vật lý riêng biệt. Phần mềm của nhà cung cấp của chúng tôi có thể cập nhật các giao dịch thực tế mà không cần cập nhật bảng cân đối thực tế, dẫn đến số dư hoạt động vượt quá giới hạn. Một hệ thống được thiết kế tốt có thể tránh điều này, nhưng hãy cẩn thận và xem xét độ chính xác quan trọng như thế nào nếu bạn đi theo cách tiếp cận bảng riêng biệt.
Ben Brocka

Tại sao đây là một yêu cầu, và những gì đang cố gắng để được thực hiện? Tùy thuộc vào nhu cầu, bạn có thể truy vấn bảng giao dịch theo yêu cầu đối với dữ liệu được chỉ định ('hiện tại') và di chuyển / tổng hợp các hàng vào cuối ngày (kho dữ liệu, mà tôi chắc chắn rằng SQL Server cung cấp tiện ích cho).
Đồng hồ-Muse

Tôi bị giới hạn trong SQL Server 2005. Tôi không phải có tổng số cuối cùng luôn chính xác, nhưng tôi phải giữ tất cả các tổng số đang chạy cho mỗi hành động được thực hiện - bảng "Lịch sử". TomTom - Tôi sẽ không giữ điều này với bảng gốc - Tôi cần một vài tổng số loại giao dịch khác nhau và chúng không thuộc về bảng gốc. Tôi không nghĩ rằng điều này chỉ có thể được thực hiện với CHỌN - đó là con trỏ hoặc vòng lặp while. Tôi thích học cách khác. X-Zero - Đây là một loại thủ tục lưu trữ dữ liệu. Tôi chỉ cần nó được thực hiện khoảng mỗi phút và không một lần một ngày.
AvnerSo

Câu trả lời:


7

Không đồng bộ ngụ ý rằng tổng số đang chạy không cần phải hoàn toàn chính xác mọi lúc hoặc các mẫu thay đổi dữ liệu của bạn sao cho tổng số bản dựng chạy một lần sẽ hợp lệ và chính xác cho đến lần tải tiếp theo. Dù sao, tôi chắc chắn rằng bạn đã nghĩ rằng một phần thông qua, vì vậy tôi sẽ không chuyển dạ.

Các tùy chọn chính của bạn cho phương thức hiệu suất cao, được hỗ trợ, là một hàm / thủ tục SQLCLR hoặc UPDATEdựa trên phương pháp lặp dựa trên tập hợp của Hugo Kornelis. Phương thức SQLCLR (được triển khai trong một quy trình, nhưng khá dễ dịch) có thể được tìm thấy ở đây .

Tôi chưa thể tìm thấy phương pháp của Hugo trên mạng, nhưng nó được trình bày chi tiết trong MVP Deep Dives xuất sắc (Tập 1). Mã mẫu để minh họa phương pháp của Hugo (được sao chép từ một trong các bài đăng của tôi trên một trang web khác mà bạn có thể không có thông tin đăng nhập) được hiển thị bên dưới:

-- 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;

Trong SQL Server 2012, bạn có thể sử dụng các phần mở rộng chức năng cửa sổ, vd SUM OVER (ORDER BY).


5

Tôi không chắc tại sao bạn muốn không đồng bộ, nhưng một vài chế độ xem được lập chỉ mục nghe giống như vé ở đây. Nếu bạn muốn có một SUM đơn giản cho một số nhóm đó là: xác định tổng chạy.

Nếu bạn thực sự muốn không đồng bộ, với 160 hàng mới mỗi giây, tổng số đang chạy của bạn sẽ luôn bị lỗi thời. Không đồng bộ có nghĩa là không có kích hoạt hoặc chế độ xem được lập chỉ mục


5

Tính toán tổng số chạy là rất chậm, cho dù bạn làm điều đó với một con trỏ hoặc với một phép nối tam giác. Sẽ rất hấp dẫn khi không chuẩn hóa, để lưu trữ tổng số đang chạy trong một cột, đặc biệt nếu bạn chọn nó thường xuyên. Tuy nhiên, như thường lệ khi bạn không chuẩn hóa, bạn cần đảm bảo tính toàn vẹn của dữ liệu không chuẩn hóa của bạn. May mắn thay, bạn có thể đảm bảo tính toàn vẹn của tổng số chạy với các ràng buộc - miễn là tất cả các ràng buộc của bạn được tin cậy, tất cả các tổng số chạy của bạn là chính xác.

Ngoài ra, theo cách này, bạn có thể dễ dàng đảm bảo rằng số dư hiện tại (chạy tổng số) không bao giờ âm - việc thực thi bằng các phương pháp khác cũng có thể rất chậm. Kịch bản sau đây thể hiện kỹ thuật.

    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

Sao chép từ blog của tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.