我们正在开发一个用于预付卡的平台,该平台基本上保存有关卡及其余额,付款等的数据。
到目前为止,我们有一个Card实体,该实体具有一个Account实体集合,并且每个Account都有一个Amount,该数量在每次存款/提款中都会更新。
团队中现在有一场辩论;有人告诉我们,这违反了Codd的12条规则,并且在每次付款时更新其值都是很麻烦的。
这真的有问题吗?
如果是,我们该如何解决?
我们正在开发一个用于预付卡的平台,该平台基本上保存有关卡及其余额,付款等的数据。
到目前为止,我们有一个Card实体,该实体具有一个Account实体集合,并且每个Account都有一个Amount,该数量在每次存款/提款中都会更新。
团队中现在有一场辩论;有人告诉我们,这违反了Codd的12条规则,并且在每次付款时更新其值都是很麻烦的。
这真的有问题吗?
如果是,我们该如何解决?
Answers:
是的,这是非标准化的,但出于性能原因,有时非标准化的设计会胜出。
但是,出于安全原因,我可能会略有不同。(免责声明:我目前不在,也从未在金融部门工作过。我只是把它扔在那里。)
在卡上有一张表,用于过帐余额。这将为每个帐户插入一行,指示每个期间(天,周,月或其他适当时间)结束时的过帐余额。通过帐号和日期对该表编制索引。
使用另一个表来暂挂插入中的未决事务。在每个期间的结束时,运行一个例程,将未过帐的交易添加到帐户的最后一个期末余额中以计算新的余额。将未完成的交易标记为已发布,或者查看日期以确定仍在进行的交易。
这样,您就可以按需计算卡余额,而不必汇总所有帐户历史记录,并且通过将余额重新计算放在专用的过账例程中,可以确保这种重新计算的交易安全性限于一个位置(并且还限制了余额表的安全性,因此只有过帐例程才能向其写入数据)。
然后只需保留审计,客户服务和性能要求所需的尽可能多的历史数据。
另一方面,我们在会计软件中经常遇到一个问题。释义:
我真的需要汇总十年的数据来找出支票帐户中有多少钱吗?
答案当然是不,你不会。这里有几种方法。一种是存储计算出的值。我不推荐这种方法,因为导致错误值的软件错误很难跟踪,因此我会避免这种方法。
更好的方法是我所说的log-snapshot-aggregate方法。在这种方法中,我们的付款和使用是插入内容,我们从不更新这些值。我们会定期汇总一段时间内的数据,并插入计算得出的快照记录,该记录代表快照有效时(通常为一段时间)的数据存在之前)。
现在,这不会违反Codd的规则,因为随着时间的推移,快照可能不太完全取决于所插入的付款/使用数据。如果有工作快照,我们可以决定清除10年之久的数据,而不会影响我们按需计算当前余额的能力。
出于性能原因,在大多数情况下,我们必须存储当前余额-否则,动态计算最终可能会变得异常缓慢。
我们确实将预先计算的运行总计存储在我们的系统中。为了保证数字始终正确,我们使用约束。以下解决方案已从我的博客复制而来。它描述了一个库存,本质上是相同的问题:
众所周知,无论是使用游标还是使用三角形联接,都要计算运行总计很慢。进行非规范化,将运行总计存储在列中非常诱人,特别是如果您经常选择它。但是,像往常一样,在进行非规格化时,需要保证非规格化数据的完整性。幸运的是,您可以保证有约束条件的运行总计的完整性–只要所有约束都受信任,您的所有运行总计都是正确的。同样,通过这种方法,您可以轻松地确保当前余额(运行总计)永远不会为负-通过其他方法强制执行也可能非常缓慢。以下脚本演示了该技术。
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
余额是根据某些业务规则计算得出的金额,因此,是的,您不想保留余额,而是根据卡上的交易以及帐户进行计算。
您想跟踪卡上用于审计和对帐单报告的所有交易,甚至以后还要记录来自不同系统的数据。
底线-计算何时需要计算的任何值