一个简单银行账户中的派生账户余额与存储账户余额有何区别?

73

与我们普通的银行账户一样,我们有许多交易会导致资金的流入或流出。账户余额可以通过简单地加总交易值来计算得出。那么,在数据库中存储更新后的账户余额,还是在需要时重新计算它,哪个更好呢?

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

预期检索账户余额:每次发生交易时以及平均每天一次。

2个回答

215

前言

存在客观真相:审计要求。此外,当处理公共资金时,必须遵守法规。

您不必实现完整的会计要求,只需实现您需要的部分即可。

相反,如果实现了与标准会计要求(其中的部分)不同的内容,那么当错误数量或负载超过某个阈值,或者系统扩展时,则会导致重新实现。这是可以避免的成本,因此应该避免。

还需要指出的是:不要雇用没有资质和认证的“审计员”。这将产生后果,就像雇用没有资质的开发人员一样。如果税务局对您处以罚款,情况可能会更糟。

方法

在不太原始的国家中,标准会计方法是这样的。在其他国家,则是“最佳实践”。

此方法适用于具有类似操作、需要、历史月度数字与当前月份要求的任何系统,如库存控制等。

考虑事项

首先,考虑事项。

  1. 永远不要重复数据。
    如果“当前余额”可以被推导出来(就像你指出的那样很简单),就不要用汇总列重复它。

    • 这样的列是数据的重复。 它破坏了规范化规则。
    • 此外,它会产生一个“更新异常”,否则不存在。
  2. 如果您确实使用了汇总列,则插入新的“账户交易”时,汇总列“当前余额”的值就会过时,因此必须始终更新它。 这是“更新异常”的结果。 这消除了其意义。

  3. 外部发布。
    另外一点,如果余额被发布,例如在月度银行对账单中,这些文件通常存在法律限制和影响,因此发布的当前余额值在发布后不得更改。

    • 在发布日期之后,在数据库中更改已经发布的数字是不诚实、欺诈等行为的证据。

      • 试图更改已发布的历史记录是新手的标志。 新手和精神病患者会坚持认为历史可以改变。 但是众所周知,对法律的无知并不构成有效的辩护。
    • 您不希望银行在2015年4月更改他们在2014年12月向您发布的“当前余额”。

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

  4. 要更正过去发生的错误的“账户交易”,正在进行的更正或调整,必须作为当月的新“账户交易”进行(即使它适用于某个以前的月份或持续时间)。

    • 这是因为适用月份已关闭; 审计完成; 并且已发布,因为一旦历史发生并记录下来后,就无法更改历史。 唯一有效的月份是当前月份。

    • 对于利息计算系统等不那么原始的国家,在发现错误后,如果具有历史影响(例如,您在2015年4月发现自12月2014以来每月计算的证券利息不正确),则更正的利息付款/扣除的价值将根据错误天数今天计算,并作为新的“账户交易”插入当月。 再次强调,唯一有效的月份是当前月份。

      当然,还必须纠正安全的利率,以避免重复此错误。

    • 相同的原则适用于库存控制系统。 它保持清晰明了。

  5. 所有真正的会计系统(即在适用国家获得审计机构认可的系统,而不是充斥着假货的“软件包”)都使用“双向记账”系统进行所有“账户交易”,这正是因为它可以防止一系列错误,其中最重要的是资金不会丢失。 这需要总分类帐和双向记账。

    • 您没有要求它,也不需要它,因此我在这里不会描述它。 但是请记住它,以防钱款“丢失”,因为这就是您必须实施的
      这个答案是针对所提出的问题,而不是关于复式记账的。
      如果需要全面了解该主题(包括详细的数据模型、会计交易示例、受影响的行以及SQL代码示例),请参考此问答:
      复式记账的关系数据模型
      6. 影响性能的主要问题不在本问题的讨论范围内,但为了提供一个简短而确定的答案:它取决于: - 是否实现真正的关系数据库(例如1960年代的记录存储系统,其特点是“记录ID”,用于方便地部署在SQL容器中)。 - 是否使用真正的SQL平台(架构;稳定;可靠;符合SQL标准;OLTP等)或伪装的SQL免费软件(一堆程序;经常更改;无法符合标准;像鱼一样扩展)。 - 使用真正的关系键等将保持高性能,无论表中的数据量如何。 - 相反,记录存储系统将表现糟糕,它们根本无法胜任。在RFS的上下文中使用“扩展”是一个欺诈性的术语:它隐藏了原因并试图解决一切除了原因之外的问题。最重要的是,这些系统没有关系完整性、关系能力或关系速度。
      7. 实施: 关系数据模型 • 银行账户

      Acct

      关系数据模型 • 库存

      Inv

      符号说明

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

      • 我的IDEF1X入门对于那些初次接触关系模型或其建模方法的人来说是必读的。请注意,IDEF1X模型非常详细和精确,显示了所有必需的细节,而自行开发的模型则远不如此。这意味着必须理解符号说明。

      内容

      1. 对于每个 AccountNo,每个月将有一个 AccountStatement 行,包括一个 ClosingBalance、账单 Date(通常是下个月的第一天)以及关闭月份的其他账单细节。

        • 这不是“重复”或可派生值,因为 (a) 该值仅适用于一个 Date,(b) 它是出于审计和健全性目的存储的,(c) 它提供了实质性的性能优势(消除了所有交易的 SUM())。

          对于库存,对于每个 PartCode,每月将有一个 PartAudit 行,其中还有一个 QtyOnHand 列。

        • 它具有附加值,约束了需要查询的交易行的范围当前月份。

          • 同样地,如果您的表是关系型的,并且您有一个 SQL 平台,则 AccountTransaction 的主键将是 (AccountNo, Transaction DateTime),这将以毫秒速度检索交易。

          • 而对于记录文件系统,“主键”将是 AccountTransactionID,并且您将通过交易日期检索当前月份,可能或可能没有正确地进行索引,并且所需的行将分布在文件中。无论如何都比聚集索引速度慢得多,并且由于分散,它将产生表扫描。

      2. AccountTransaction 表保持简单(银行账户交易的现实概念是简单的)。它有一个单一的正数 Amount 列。

      3. 对于每个 AccountCurrentBalance 是:

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

          • 对于库存,则为 PartAudit.QtyOnHand
        • 加上当前月份的 SUM( Transaction.Amounts ),其中 AccountTransactionType 表示存款

          • 对于库存,则为 PartMovement.Quantity
        • 减去当前月份的 SUM( Transaction.Amount ),其中 AccountTransactionType 表示取款

        • (以下提供代码)。

      4. 在此方法中,当前月份的 AccountTransactions 处于不确定状态,因此必须检索。所有之前的月份都已经发布并关闭,因此必须使用审计数字 AccountStatement.ClosingBalance

      5. 可以清除 AccountTransaction 表中的旧行。公共资金超过十年,其他资金超过五年,业余俱乐部系统超过一年。

      6. 当然,任何与会计系统相关的代码都必须使用真正的 OLTP 标准和真正的 SQL ACID 事务(在假SQL免费软件中不可能)。

      7. 该设计包含了所有范围级性能考虑因素(如果这不明显,请要求扩展)。数据库内的扩


        纠正性建议

        这些内容需要说明,因为在许多SO答案中提供了不正确的建议(当然是由大众民主投票支持的),而互联网上充斥着不正确的建议(业余爱好者喜欢发布他们的主观“真理”):

        我已经给出了一种技术术语的方法,可以针对清晰的数据模型进行操作。因此,它不是针对特定国家特定应用程序的伪代码。该方法适用于有能力的开发人员,对于需要手把手指导的人来说不够详细。
        他们还不理解一个月的截止期限只是一个例子:如果您的税务局截止期限是季度性的,那么请使用季度性的截止期限;如果您唯一的法律要求是年度报告,请使用年度报告。
        即使您的截止期限是为了外部或合规目的而设定的季度性,公司也可能会选择每月截止,以进行内部审计和卫生检查(即将状态流变的时间长度最小化)。
        在原始国家和流氓国家,银行保持其状态流变周期最长,这是显然的恶意目的之一。其中一些只在年度提交合规报告。这就是澳大利亚银行不会破产的原因之一。
        在AccountTransaction表中,不要在Amount列中使用负/正。金钱总是有正值的,没有负二十美元(或者你欠我负五十美元),然后算出双重否定意味着其他事情。
        资金运动方向或您将使用资金的方式是一个单独且离散的事实(对于AccountTransaction.Amount)。这需要一个单独的列(一个数据中的两个事实会打破规范化规则,从而引入代码复杂性)。
        实现AccountTransactionType参考表,其主键为(D,W)表示存款/取款作为起点。随着系统的发展,只需添加(A,a,F,w)表示调整信贷; 调整借记; 银行手续费; ATM提款等。
        在一些原始国家,诉讼要求规定,在列出交易的任何报告中,必须在每一行上显示累计总额。(请注意,这不是审计要求,因为这些要求优先于法院要求;审计师比律师聪明得多等)。
        显然,我不会反对法院的要求。问题在于,原始编码器将其转换为:哦,我们必须实现AccountTransaction.CurrentBalance列。他们未能理解:
        在报告中打印列的要求不是在数据库中存储值的命令
        任何类型的累计总和都是一个派生值,很容易编码(如果您觉得很难,请发布问题)。只需在报告中实现所需的代码即可。
        将运行总数(例如AccountTransaction.CurrentBalance)实现为列会导致可怕的问题:
        引入了一个重复的列,因为它是可派生的。违反规范化。引入更新异常。
        在上述情况下,提交给法院使用的报告现在已过时(在线数据的每个报告在打印时都已过时)。即。打印;审核;更改交易;重新打印;重新审核,直到您满意为止。它毫无意义。
        这就是为什么在不太原始的国家中,法院不接受任何旧的印刷纸张,他们只接受已发布的数字,例如银行对账单,这些数字已经符合审计要求(请参阅上面的方法),并且不能被撤回或更改并重新打印。

        评论

        Alex:
        是的,代码很好看,谢谢。甚至可以提供一个示例的“黑店”,让人们能够一次性地看到起始模式,这将使世界变得更美好。

        对于上面的数据模型。

        代码•报告当前余额

        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"
        

        通过对交易日志进行去规范化,我可以以更方便的查询和更少的视图/物化视图更改来交换正常形式,当我添加更多的tx类型时。

        天哪,帮帮我吧。

        当你违反标准时,你会把自己置于第三世界的位置,那里的东西本不应该破裂,在第一世界国家从来没有破裂过。
        从权威处寻求正确答案,然后反对它,或者为你的次标准方法辩护,这可能不是个好主意。
        非规范化(此处)会导致更新异常,重复的列可以从TransactionTypeCode中派生出来。你想要编码的简便性,但你愿意在两个地方编码,而不是一个地方。这正是容易出错的代码。
        根据E F Codd博士的关系模型,完全规范化的数据库提供了最简单、最逻辑、最直接的代码。(在我的工作中,我按合同保证每个报告都可以由一个SELECT服务。)
        ENUM不是SQL。(免费的NONsql套件没有SQL兼容性,但它们有不需要的额外功能。)如果你的应用程序毕业到商业SQL平台,你将不得不将所有这些ENUMs重写为普通的查找表。使用CHAR(1)或INT作为PK。然后你会意识到它实际上是一个带有PK的表。
        错误的值为零(它也有负面影响)。真相的价值为一。我不会用零来换取一。因此这不是一个权衡。这只是你的开发决定。

1
@Alex。1)Credit_Debit_Type加上TransactionType会是非逻辑的,而且它还会破坏规范化。因为TransactionType 绝对 决定了Credit_Debit_Type。2)你不知道如何使用CASE语句吗?没有额外的代码需要调用。实际上,检查两个列比检查一个列需要更多的代码。 - PerformanceDBA
1
@Alex。这两个INSERTS将是D。数据模型不显示GeneralLedger(内部银行账户),例如BankCash。它不是同一张表,会有更多的列。这些账户取决于报告:如果你在计算现金,那就是那个。Asset/Liability/etc定义了处理方式,以及其外部的AccountNo。你对SUM()的概念不适用。运算符适用。 - PerformanceDBA
1
我认为,没有缺失实体的剥离模式非常令人困惑。总是有借方和贷方,如果你向一个账户存入资金,你必须从另一个账户取出相应金额。插入两行“存款”交易类型代码并不能真正显示给我方向,它只是将相同金额添加到两个不同的“账户”中。这里的价值方向在哪里?“账户类型”也是一个奇怪的属性,它是如何对“账户”进行分类的呢?也许是因为这个模式中缺少其他实体(那些著名的“银行现金”和“GL”)。 - Alex
1
我猜在一个用户可以存款、转账和提现资金的系统中,必须存在一个“日志”,其中系统记录所有“业务”交易,例如“用X换Y”,“从John向Mary进行内部转账”,“从他们每个人那里收取月费”。对于“Journal”中的每个条目,都有一个或多个(以2的倍数)“AccountTransaction”,并且“Journal”中的1行按“JournalType”分类,并在相关表中存储特定业务交易的额外数据作为“Journal”中的条目。 - Alex
1
@Alex。1st comment) 是的。日记表没有显示(如声明),这是执行双重输入交易的地方。AccountType是外部{支票,储蓄...},而不是内部银行GL帐户类型。请提出一个新问题,以便我可以全面处理它。2nd) 是的。每个4部分账户::GL集合都是ACID事务,但是“所有月费”不是(它是批处理作业)。 - PerformanceDBA
显示剩余16条评论

1
这是相当主观的。我建议考虑以下因素:
1.当前有多少个帐户? 2.未来预计会有多少帐户? 3.您对可伸缩性的重视程度有多大? 4.更新数据库和代码以跟踪余额作为自己的字段有多难? 5.是否有更紧急的开发问题需要解决?
就所提出的两种方法的优点而言,按需汇总交易价值很可能是更容易/更快实现的方法。
然而,它不如将当前账户余额作为数据库中的一个字段进行维护并在进行交易时进行更新那样具有可伸缩性。并且它会增加整体交易处理时间,因为每个交易都需要运行查询来计算当前账户余额才能继续。在实践中,除非您拥有非常大量的帐户/交易或者预计在不久的将来会有,否则这些可能是小问题。
第二种方法的缺点是,最初设置它可能需要更多的开发时间/精力,并且可能需要考虑如何在帐户内同步交易,以确保每个交易始终准确地查看和更新余额。

因此,这主要取决于项目的需求,在当前开发时间最好花费的地方,以及现在是否值得未来证明解决方案,而不是在性能和可扩展性成为真正的问题时实施第二种方法。


嘿Aroth,感谢您的意见!所以,1)目前应用程序正在开发中,因此只有测试用户;2)和3)它将需要扩展,但不是在不久的将来(比如说,我们至少一年内不必担心);4)更新数据库和代码以进行跟踪非常简单,只需在代码中添加另一个部分并在数据库中进行另一个更新即可;5)是的,还有其他问题,这个会计是项目的一小部分。我也觉得当它真正扩大时,需要采取第二种方法。 - Anmol Gupta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接