一个简单银行账户的派生账户余额与存储账户余额?

Derived account balance vs stored account balance for a simple bank account?

所以,这就像我们的普通银行账户一样,我们有很多交易导致资金流入或流出。账户余额总是可以通过简单地总结交易价值来得出。在这种情况下,将更新后的帐户余额存储在数据库中或在需要时重新计算它会更好吗?

每个账户的预期交易量:<5 每天

预期检索帐户余额:每当交易发生时,否则平均每天一次。

您建议如何对此做出决定? 非常感谢!

这是相当主观的。我建议考虑的事项是:

  1. 目前有多少个帐户?
  2. 您希望将来拥有多少个帐户?
  3. 您认为可扩展性有多重要?
  4. 更新数据库和代码以跟踪余额作为自己的字段有多难?
  5. 是否有更紧迫的发展问题需要关注?

就所提出的两种方法的优点而言,按需求和交易价值很可能是 easier/quicker 实施方法。

但是,它无法扩展,也无法将当前帐户余额作为数据库中的一个字段来维护并随时更新。而且它会稍微增加您的整体交易处理时间,因为每笔交易都需要 运行 一个查询来计算当前账户余额,然后才能继续。在实践中,除非您有大量 accounts/transactions 或预计在不久的将来会有,否则这些可能是小问题。

第二种方法的缺点是它可能需要更多开发 time/effort 才能进行初始设置,并且可能需要您考虑如何同步帐户内的交易以确保每个任何时候都可以准确地查看和更新​​余额。

所以这主要归结为项目的需求是什么,目前最好将开发时间花在什么地方,以及是否值得现在就对解决方案进行未来验证,而不是稍后实施第二种方法,当性能和可扩展性成为现实问题,而不是理论上的问题。

Preface

There is an objective truth: Audit requirements. Additionally, when dealing with public funds, there is Legislature that must be complied with.

You don't have to implement the full accounting requirement, you can implement just the parts that you need.

Conversely, it would be ill-advised to implement something other than the standard accounting requirement (the parts thereof) because that guarantees that when the number of bugs or the load exceeds some threshold, or the system expands,you will have to re-implement. A cost that can, and therefore should, be avoided.

It also needs to be stated: do not hire an unqualified, un-accredited "auditor". There will be consequences, the same as if you hired an unqualified developer. It might be worse, if the Tax Office fines you.

方法

not-so-primitive 个国家的标准会计方法是这样的。 "best practice",如果你愿意,在其他地方。

该方法适用于任何有类似操作的系统;需要;历史月度数据与 current-month 要求,例如库存控制等

考虑

一、注意事项

  1. 切勿重复数据。
    如果 Current Balance 可以导出(这里很简单),不要用汇总列复制它。这样的列是数据的重复。它打破了规范化规则。此外,它创建一个更新异常,否则不存在。

  2. 如果您确实使用了汇总列,则每当更新交易时(如已更改,而不是在插入新交易时),汇总列 value 变得过时了,所以它必须一直更新。这是更新异常的结果。这消除了拥有它的价值。

  3. 外部public化。
    分开点。如果余额已公布,如月度银行对帐单,此类文件通常具有法律限制和影响,因此公布的当前余额值在 public 后不得更改。

    • 在 public 发布日期之后,数据库中对外发布的数字的任何更改都是不诚实行为、欺诈等行为的证据。

      • 这种试图改变公开历史的行为是新手的标志。新手和精神病人会坚持认为历史是可以改变的。但是大家应该知道,对法律的无知并不能构成有效的辩护。
    • 您不希望您的银行在 2015 年 4 月更改他们在 2014 年 12 月向您发布的银行对帐单中公布的当前余额。

    • 该数字必须被视为审计数字,已发布且不可更改。

  4. 为了更正过去犯下的错误,现在正在更正,必要的更正或调整作为当月的新交易进行(即使它适用于之前的某个月份或持续时间)。

    这是因为 applicable-to 月份已经结束;审计;并发表,因为历史已经发生并被记录下来,就无法改变历史。唯一有效的月份是当前月份。

    • 对于 interest-bearing 系统等,在 not-so-primitive 国家/地区,当发现错误并具有历史影响时(例如,您在 2015 年 4 月发现自 2014 年 12 月以来,对证券计算的利息一直不正确),修正后的利息 payment/deduction 的价值是今天计算的,计算错误的天数,并将总和作为交易插入到这个月。同样,唯一有效的月份是当前月份。

      当然,证券的利率也必须修正,这样错误就不会重复。

    • 如果您发现银行计算您的储蓄 (interest-bearing) 帐户的利息有误,并且您已更正错误,您将获得一笔存款,即构成全部调整值,当月。那是当月的一笔交易。

      银行不会:更改历史记录;对每个历史月份应用利息;回顾历史性的银行对账单; re-publish 历史性的银行对账单。不。也许在第三世界国家除外。

    • 同样的原则也适用于库存控制系统。它保持理智。

  5. 所有真实的会计系统(即那些由适用国家/地区的审计机构认可的系统,而不是比比皆是的米老鼠"packages")都使用双重输入系统交易,正是因为它防止了一大堆错误,其中最重要的是,资金没有得到"lost"。这需要总帐和 Double-Entry 会计。

    • 你没有要求,你不需要,所以我不在这里描述。但是请记住,万一钱花掉了 "missing",因为那是您必须实施的,而不是某些 band-aid 解决方案;还没有另一个未经认可的 "package".

    This Answer services the Question that is asked, which is not Double-Entry Accounting.
    For a full treatment of that subject (detailed data model; examples of accounting Transactions; rows affected; and SQL code examples), refer to this Q&A:
    .

  6. 影响性能的主要问题超出了这个问题的范围,它们与您是否实现了真正的关系数据库有关(例如,1960 年代的记录归档系统,它是以 Record IDs 为特征,为方便起见部署在 SQL 数据库容器中)。

    • 使用真正的关系键等将保持高性能,无论 tables 的人口如何。

    • 相反,RFS 会表现得很糟糕,他们根本无法表现。 "Scale" 在 RFS 的上下文中使用时,是一个欺诈性术语:它隐藏了原因并试图解决除原因之外的所有问题。最重要的是,此类系统具有 none 的关系完整性;关系力量;或关系系统的关系速度。

实施

关系数据模型 • 账户余额

关系数据模型 • 库存

符号

  • 我所有的数据模型都在 IDEF1X 中呈现,这是自 1993 年以来的关系数据库建模标准。

  • 我的 IDEF1X Introduction 对于刚接触 关系模型 的人来说是必不可少的读物,或者其建模方法。请注意,IDEF1X 模型具有丰富的细节和精度,显示了所有必需的细节,而 home-grown 模型远不及此。这意味着,符号必须被理解。

内容

  1. 对于每个帐户,将有一个 ClosingBalance,在 AccountStatement table 中(每个 AccountNo 每月一行),以及带有报表日期(通常是每月的第一天)和其他报表详细信息。

    • 这不是重复的,因为出于审计和健全目的需要它。

      对于库存,它是 QtyOnHand 列,在 PartAudit table 中(每个 PartCode 每月一行)

    • 它还有一个附加值,就是把需要查询的Transaction行的范围限制在当月

      • 同样,如果您的 table 是关系型的,AccountTransaction 的主键将是(AccountNo,事务 DateTime),它将检索以毫秒速度进行交易。

      • 而对于记录归档系统,"primary key" 将是 TransactionID,您将按交易日期检索当前月份,这可能会或可能不会被索引正确,并且所需的行将分布在整个文件中。在任何情况下都远低于 ClusteredIndex 速度,并且由于传播,它会招致 table 扫描。

  2. AccountTransactiontable保持简单(银行账户交易的现实世界概念很简单)。它有一个正 Amount 列。

  3. 对于每个 AccountCurrentBalance 是:

    • 上个月的AccountStatement.ClosingBalance,为方便起见,日期为下个月的第一天

      (对于库存,PartAudit.QtyOnHand

    • 加上当月AccountTransaction.Amounts的SUM,其中TransactionType表示入金

      (对于库存,PartMovement.Quantity

    • 减去当月AccountTransaction.Amounts的SUM,其中`MovementType表示出金。

  4. 在这个方法中,只有当月的AccountTransactions处于不断变化的状态,因此必须导出。之前的所有月份均已发布并关闭,因此必须使用审计数字

  5. 可以清除 AccountTransaction table 中较旧的行。超过十年 public 金钱,五年,业余爱好俱乐部系统一年。

  6. 当然,任何与会计系统相关的代码都必须使用真正的 OLTP 标准和真正的 SQL ACID 事务。

  7. 这个设计包含了所有scope-level性能方面的考虑(如果这不是很明显,请要求扩展)。数据库内部的扩展是一个 non-issue,任何仍然存在的扩展问题都是在数据库外部。


纠正建议

这些项目需要说明只是因为许多 SO Answers 中提供了不正确的建议(当然还有 up-voted 大众,民主地提供),并且互联网 chock-full 不正确建议(业余爱好者喜欢发表自己的主观意见"truths"):

  1. 很明显,有些人不明白我给出了一个专业术语的方法,来操作一个清晰的数据模型。因此,它不是针对特定国家/地区的特定应用程序 pseudo-code。方法是给有能力的开发者的,对于需要手把手的开发者来说不够详细

    • 他们也不明白一个月的 cut-off 期间是一个 例子:如果你的 cut-off 税务局用途是季刊,那就一定要用季刊cut-off;如果您唯一的法律要求是年度,请使用年度。

    • 即使您的 cut-off 出于外部或合规目的每季度一次,公司也可能选择每月一次 cut-off,用于内部审计和理智目的(即保持通量状态周期的长度到最小值)。

      例如。在澳大利亚,税务局 cut-off 的企业是每季度一次,但较大的公司 cut-off 他们的库存控制是每月一次(这样可以避免长时间追逐错误)。

      例如。银行每月都有法律合规要求,因此他们对数据进行内部审计,并每月关闭账簿。

    • 在原始国家和流氓国家,出于明显的邪恶目的,银行将其 state-of-flux 期限保持在最大值。他们中的一些人每年只制作合规报告。这就是为什么澳大利亚的银行不会倒闭的原因之一。

  2. AccountTransactiontable中,不要在Amount栏中使用negative/positive。钱总是有正值,没有负二十美元(或者 你欠我五十美元)这样的东西,然后计算出双重否定意味着别的东西。

  3. 运动方向,或者你打算用资金做什么,是一个独立的、离散的事实(AccountTransaction.Amount)。这需要一个单独的列(一个数据中的两个事实违反了规范化规则,结​​果是它给代码带来了复杂性)。

    • 实施 TransactionType 参考 table,其主键是 Deposit/Withdrawal 的 (D, W ) 作为您的起点。随着系统的增长,只需​​为 Adjustment Credit 添加 ( A, a, F, w );调整借方;银行费用; ATM_Withdrawal;等等

    • 无需更改代码。

  4. 在一些原始国家,诉讼要求规定在任何列出交易的报告中,每一行都必须显示 运行 总数。 (请注意,这不是审计要求,因为这些要求优于 [(参考上述方法)法院要求;审计员比律师更愚蠢;等等)

    显然,我不会反对法院的要求。问题是原始编码器将其翻译成:哦,哦,我们必须实现一个 AccountTransaction.CurrentBalance 。他们不明白:

    • 在报表上打印列的要求并不要求在数据库中存储值

    • a 运行 任何类型的总和都是派生值,并且很容易编码(post 如果这对您来说不容易,请问这个问题)。只需在报告中执行所需的代码即可。

    • 实施 运行 总数,例如。 AccountTransaction.CurrentBalance 作为列会导致可怕的问题:

      • 引入了一个重复的列,因为它是可推导的。打破规范化。引入更新异常。

      • 更新异常:每当历史上插入一个事务,或更改一个AccountTransaction.Amount,所有AccountTransaction.CurrentBalances从那个日期到现在 必须 re-computed 并更新。

    • 在上述案例中,提交给法庭使用的报告现在已经过时(每份在线数据报告在打印时就已过时)。 IE。打印;审查;更改交易; re-print; re-review,直到你开心为止。无论如何都是没有意义的。

    • 这就是为什么在 less-primitive 国家,法院不接受任何旧印刷品,他们只接受出版的数字,例如。 Bank Statements,已经符合审计要求(参考上述方法),并且不能被召回或更改和re-printed.


评论

Alex:
yes code would be nice to look at, thank you. Even maybe a sample "bucket shop" so people could see the starting schema once and forever, would make world much better.

对于上面的数据模型。

代码 • 报告当前余额

SELECT  AccountNo,
        ClosingDate = DATEADD( DD, -1 Date ), -- show last day of previous
        ClosingBalance,
        CurrentBalance = ClosingBalance + (
            SELECT SUM( Amount )
                FROM AccountTransaction
                WHERE AccountNo = @AccountNo
                    AND TransactionTypeCode IN ( "A", "D" )
                    AND DateTime >= CONVERT( CHAR(6), GETDATE(), 2 ) + "01"
                ) - (
            SELECT SUM( Amount )
                FROM AccountTransaction
                WHERE AccountNo = @AccountNo
                    AND TransactionTypeCode NOT IN ( "A", "D" )
                    AND DateTime >= CONVERT( CHAR(6), GETDATE(), 2 ) + "01"
                )
    FROM AccountStatement
    WHERE AccountNo = @AccountNo
        AND Date = CONVERT( CHAR(6), GETDATE(), 2 ) + "01"

By denormalising that transactions log I trade normal form for more convenient queries and less changes in views/materialised views when I add more tx types

上帝保佑我。

  1. 当你违反标准时,你将自己置于 third-world 的位置,在 first-world 国家/地区,不应该打破的东西永远不会打破。

    向权威寻求正确的答案,然后争论它,或者争论你的sub-standard方法可能不是一个好主意。

  2. 非规范化(此处)导致更新异常,重复的列,可以从 TransactionTypeCode 派生。您想要简化编码,但您愿意在两个地方而不是一个地方进行编码。那正是那种容易出错的代码。

    根据 Dr E F Codd 的 关系模型 完全规范化的数据库提供了最简单、最合乎逻辑的 straight-forward 代码。 (在我的工作中,我根据合同保证每一份报告都可以由一个 SELECT 提供服务。)

  3. ENUM 不是 SQL。 (免费软件 NONsql 套件没有 SQL 合规性,但它们确实具有 SQL 中不需要的额外功能。)如果您的应用程序升级到商业 SQL 平台,您将必须re-write 所有这些 ENUMs 作为普通查找 table。以 CHAR(1)INT 作为 PK。然后你会欣赏到它其实是一个PKtable

  4. 错误的值为零(它也有负面后果)。真理的值为一。我不会用一换零。因此它不是 trade-off。这只是您的开发决定。