Có thể giữ một giá trị cập nhật trong bảng không?


31

Chúng tôi đang phát triển một nền tảng cho thẻ trả trước, về cơ bản chứa dữ liệu về thẻ và số dư, thanh toán, v.v.

Cho đến nay, chúng tôi đã có một thực thể Thẻ có một tập hợp các thực thể Tài khoản và mỗi Tài khoản có một Số tiền, cập nhật trong mỗi Khoản tiền gửi / Rút tiền.

Có một cuộc tranh luận bây giờ trong đội; ai đó đã nói với chúng tôi rằng điều này phá vỡ 12 Quy tắc của Codd và việc cập nhật giá trị của nó trên mỗi khoản thanh toán là rắc rối.

Đây thực sự là một vấn đề?

Nếu có, làm thế nào chúng ta có thể khắc phục điều này?


3
Có một cuộc thảo luận kỹ thuật rộng rãi về chủ đề này ở đây trên DBA.SE: Viết một lược đồ ngân hàng đơn giản
Nick Chammas

1
Nhóm nào của Codd đã trích dẫn ở đây? Các quy tắc là nỗ lực của anh ta để xác định một hệ thống quan hệ, và không đề cập đến bình thường hóa rõ ràng. Codd đã thảo luận về chuẩn hóa trong cuốn sách của ông Mô hình quan hệ để quản lý cơ sở dữ liệu .
Iain Samuel McLean Elder

Câu trả lời:


30

Vâng, đó là không bình thường hóa, nhưng đôi khi các thiết kế không chuẩn hóa chiến thắng vì lý do hiệu suất.

Tuy nhiên, tôi có thể sẽ tiếp cận nó một chút khác nhau, vì lý do an toàn. (Tuyên bố miễn trừ trách nhiệm: Hiện tại tôi chưa từng làm việc trong lĩnh vực tài chính. Tôi chỉ ném nó ra khỏi đó.)

Có một bảng cho số dư được đăng trên thẻ. Điều này sẽ có một hàng được chèn cho mỗi tài khoản, cho biết số dư được đăng vào cuối mỗi kỳ (ngày, tuần, tháng hoặc bất cứ điều gì phù hợp). Lập chỉ mục bảng này theo số tài khoản và ngày.

Sử dụng một bảng khác để giữ các giao dịch đang chờ xử lý, được chèn nhanh chóng. Vào cuối mỗi kỳ, hãy chạy một thói quen bổ sung các giao dịch chưa được đăng vào số dư cuối kỳ của tài khoản để tính số dư mới. Đánh dấu các giao dịch đang chờ xử lý là đã đăng hoặc xem ngày để xác định những gì vẫn đang chờ xử lý.

Bằng cách này, bạn có phương tiện tính toán số dư thẻ theo yêu cầu, mà không phải tổng hợp tất cả lịch sử tài khoản và bằng cách đặt tính toán lại số dư trong thói quen đăng bài chuyên dụng, bạn có thể đảm bảo rằng an toàn giao dịch của tính toán lại này được giới hạn một vị trí duy nhất (và cũng giới hạn bảo mật trên bảng cân đối để chỉ thói quen đăng bài có thể ghi vào đó).

Sau đó, chỉ cần giữ nhiều dữ liệu lịch sử cần thiết bằng cách kiểm toán, dịch vụ khách hàng và các yêu cầu về hiệu suất.


1
Chỉ cần hai ghi chú nhanh. Đầu tiên, đó là một mô tả rất hay về phương pháp chụp nhanh tổng hợp nhật ký mà tôi đã đề xuất ở trên, và có lẽ rõ ràng hơn tôi. (Nâng cao bạn). Thứ hai, tôi nghi ngờ bạn đang sử dụng thuật ngữ "được đăng" hơi kỳ lạ ở đây, có nghĩa là "một phần của số dư cuối kỳ". Về mặt tài chính, được đăng thường có nghĩa là "hiển thị trong số dư sổ cái hiện tại" và do đó có vẻ như nó đáng để giải thích rằng vì vậy nó không gây nhầm lẫn.
Chris Travers

Vâng, có lẽ có rất nhiều sự tinh tế mà tôi đang bỏ lỡ. Tôi chỉ đề cập đến cách các giao dịch dường như được "đăng" vào tài khoản kiểm tra của tôi khi gần kết thúc kinh doanh và số dư được cập nhật tương ứng. Nhưng tôi không phải là kế toán viên; Tôi chỉ làm việc với một vài người trong số họ.
db2

Đây cũng có thể là một yêu cầu đối với SOX hoặc tương tự trong tương lai, tôi không biết chính xác loại yêu cầu giao dịch vi mô nào bạn phải đăng nhập, nhưng tôi sẽ hỏi ai đó biết yêu cầu báo cáo là gì sau này.
jcolebrand

Tôi sẽ có xu hướng giữ dữ liệu vĩnh viễn, ví dụ như số dư vào đầu mỗi năm, để ảnh chụp nhanh "tổng số" không bao giờ bị ghi đè - danh sách chỉ đơn giản được thêm vào (ngay cả khi hệ thống được sử dụng đủ lâu cho mỗi tài khoản tích lũy 1.000 tổng số hàng năm [ RẤT lạc quan], điều đó khó có thể quản lý được). Giữ nhiều tổng số hàng năm sẽ cho phép mã kiểm toán xác nhận rằng các giao dịch giữa các năm gần đây có tác động đúng đến tổng số [các giao dịch riêng lẻ có thể bị thanh trừng sau 5 năm, nhưng sau đó sẽ được xử lý tốt].
supercat

17

Mặt khác, có một vấn đề mà chúng tôi gặp phải thường xuyên trong phần mềm kế toán. Diễn giải:

Tôi có thực sự cần tổng hợp mười năm dữ liệu để tìm hiểu số tiền trong tài khoản kiểm tra không?

Câu trả lời tất nhiên là không có bạn. Có một vài cách tiếp cận ở đây. Một là lưu trữ giá trị tính toán. Tôi không khuyến nghị phương pháp này vì các lỗi phần mềm gây ra các giá trị không chính xác rất khó theo dõi và vì vậy tôi sẽ tránh phương pháp này.

Một cách tốt hơn để làm điều đó là cách tôi gọi là phương pháp tổng hợp log-snapshot. Theo cách tiếp cận này, các khoản thanh toán và sử dụng của chúng tôi là các phần chèn và chúng tôi không bao giờ cập nhật các giá trị này. Định kỳ chúng tôi tổng hợp dữ liệu trong một khoảng thời gian và chèn một bản ghi ảnh chụp nhanh được tính toán đại diện cho dữ liệu tại thời điểm ảnh chụp nhanh có hiệu lực (thường là một khoảng thời gian trước khi hiện tại).

Bây giờ, điều này không phá vỡ quy tắc của Codd vì theo thời gian, các ảnh chụp nhanh có thể ít hơn hoàn toàn phụ thuộc vào dữ liệu thanh toán / sử dụng được chèn. Nếu chúng tôi có ảnh chụp nhanh hoạt động, chúng tôi có thể quyết định lọc dữ liệu 10 năm tuổi mà không ảnh hưởng đến khả năng tính toán số dư hiện tại theo yêu cầu.


2
Tôi có thể lưu trữ tổng số tính toán đang chạy và tôi hoàn toàn an toàn - các ràng buộc đáng tin cậy đảm bảo số của tôi luôn chính xác: sqlblog.com/bloss/alexander_kuznetsov/archive/2009/01/23/ canh
AK

1
Không có trường hợp cạnh trong giải pháp của tôi - một ràng buộc đáng tin cậy sẽ không cho phép bạn quên bất cứ điều gì. Tôi không thấy bất kỳ nhu cầu thực tế nào về số lượng NULL trong một hệ thống thực tế cần biết tổng số đang chạy - những điều này đối với nhau. Nếu bạn thấy một nhu cầu thực tế, xin vui lòng chia sẻ sceanrio của bạn.
AK

1
Ok, nhưng sau đó điều này sẽ không hoạt động như trên db, cho phép nhiều NULL mà không vi phạm tính duy nhất, phải không? Ngoài ra bảo đảm của bạn trở nên tồi tệ nếu bạn lọc dữ liệu trong quá khứ, phải không?
Chris Travers

1
Ví dụ: nếu tôi có một ràng buộc duy nhất đối với (a, b) trong PostgreSQL, tôi có thể có nhiều giá trị (1, null) cho (a, b) vì mỗi null được coi là có khả năng duy nhất, mà tôi nghĩ là không chính xác về mặt ngữ nghĩa giá trị .....
Chris Travers

1
Về "Tôi có một ràng buộc duy nhất đối với (a, b) trong PostgreSQL, tôi có thể có nhiều giá trị (1, null)" - trong PostgreSql, chúng tôi cần sử dụng một chỉ mục một phần duy nhất trên (a) trong đó b là null.
AK

7

Vì lý do hiệu suất, trong hầu hết các trường hợp, chúng tôi phải lưu trữ số dư hiện tại - nếu không, việc tính toán nó nhanh chóng có thể trở nên chậm chạp.

Chúng tôi lưu trữ tổng số chạy được tính toán trước trong hệ thống của chúng tôi. Để đảm bảo rằng các số luôn luôn chính xác, chúng tôi sử dụng các ràng buộc. Các giải pháp sau đây đã được sao chép từ blog của tôi. Nó mô tả một hàng tồn kho, về cơ bản là cùng một vấn đề:

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

Tôi nhận thấy rằng một trong những giới hạn rất lớn trong cách tiếp cận của bạn là việc tính toán số dư tài khoản vào một ngày lịch sử cụ thể vẫn cần phải tổng hợp, trừ khi bạn cũng đưa ra giả định rằng tất cả các giao dịch được nhập theo tuần tự (thường là xấu giả định).
Chris Travers

@ChrisTravers tất cả các tổng số đang chạy luôn được cập nhật, cho tất cả các ngày lịch sử. Những ràng buộc đảm bảo rằng. Vì vậy, không cần tổng hợp cho bất kỳ ngày lịch sử. Nếu chúng tôi phải cập nhật một số hàng lịch sử hoặc chèn một cái gì đó lạc hậu, chúng tôi sẽ cập nhật tất cả các tổng số chạy của các hàng sau này. Tôi nghĩ rằng điều này dễ dàng hơn nhiều trong postgreSql, bởi vì nó có các ràng buộc hoãn lại.
AK

6

Đây là một câu hỏi rất hay.

Giả sử rằng bạn có một bảng giao dịch lưu trữ từng khoản ghi nợ / tín dụng, không có gì sai với thiết kế của bạn. Trên thực tế, tôi đã làm việc với các hệ thống telco trả trước đã hoạt động chính xác theo cách này.

Điều chính bạn cần làm là đảm bảo rằng bạn đang thực hiện SELECT ... FOR UPDATEsố dư trong khi bạn INSERTghi nợ / tín dụng. Điều này sẽ đảm bảo số dư chính xác nếu xảy ra sự cố (vì toàn bộ giao dịch sẽ được khôi phục).

Như những người khác đã chỉ ra, bạn sẽ cần một ảnh chụp nhanh về số dư tại các khoảng thời gian cụ thể để xác minh rằng tất cả các giao dịch trong một khoảng thời gian nhất định với số dư đầu kỳ / kết thúc chính xác. Viết một công việc hàng loạt chạy vào nửa đêm vào cuối giai đoạn (tháng / tuần / ngày) để làm việc này.


4

Số dư là số tiền được tính toán dựa trên các quy tắc kinh doanh nhất định, do đó, bạn không muốn giữ số dư mà chỉ tính toán nó từ các giao dịch trên thẻ và do đó là tài khoản.

Bạn muốn theo dõi tất cả các giao dịch trên thẻ để kiểm tra và báo cáo báo cáo, và thậm chí dữ liệu từ các hệ thống khác nhau sau này.

Dòng dưới cùng - tính toán bất kỳ giá trị nào cần được tính toán và khi bạn cần


thậm chí nếu có thể có 1000 giao dịch? Vì vậy, tôi sẽ cần phải tính toán lại mỗi lần? nó có thể là một chút khó khăn về hiệu suất? bạn có thể thêm một chút về lý do tại sao đây là một vấn đề như vậy?
Mithir

2
@Mithir Bởi vì nó đi ngược lại hầu hết các quy tắc kế toán, và nó làm cho các vấn đề không thể theo dõi. Nếu bạn chỉ cập nhật tổng số đang chạy, làm thế nào để bạn biết những điều chỉnh nào đã được áp dụng? Hóa đơn đó có được ghi có một hoặc hai lần không? Chúng tôi đã khấu trừ số tiền thanh toán? Nếu bạn theo dõi các giao dịch bạn biết câu trả lời, nếu bạn theo dõi tổng số bạn không.
JNK

4
Tham chiếu đến các quy tắc của Codd là nó phá vỡ hình thức bình thường. Giả sử bạn theo dõi các giao dịch SOMEWHERE (mà tôi sẽ phải nghĩ) và bạn có tổng số hoạt động riêng biệt, điều này có đúng nếu chúng không đồng ý? Bạn cần một phiên bản duy nhất của sự thật. Không khắc phục sự cố hiệu suất cho đến khi / trừ khi nó thực sự tồn tại.
JNK

@JNK theo cách hiện tại - chúng tôi giữ các giao dịch và tổng cộng, vì vậy mọi thứ bạn đề cập đều có thể được theo dõi một cách hoàn hảo nếu cần, tổng số dư chỉ để ngăn chúng tôi tính toán lại số tiền mỗi hành động.
Mithir

2
Bây giờ, nó sẽ không phá vỡ quy tắc của Codd nếu dữ liệu cũ chỉ có thể được giữ lại trong 5 năm, phải không? Số dư tại thời điểm đó không chỉ đơn thuần là tổng của các hồ sơ hiện có, mà còn là các hồ sơ hiện có trước đó kể từ khi bị thanh trừng, hoặc tôi đang thiếu một cái gì đó? Dường như với tôi, nó sẽ chỉ phá vỡ các quy tắc của Codd nếu chúng ta giả sử việc lưu giữ dữ liệu vô hạn, điều này khó xảy ra. Điều này được nói vì lý do tôi nói dưới đây, tôi nghĩ rằng việc lưu trữ một giá trị cập nhật liên tục là yêu cầu rắc rối.
Chris Travers
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.