因此,就像我们的普通银行账户一样,我们有很多交易导致资金流入或流出。账户余额总是可以通过简单地总结交易价值来得出。在这种情况下,将更新的帐户余额存储在数据库中或在需要时重新计算它会更好吗?
每个账户的预期交易量:<5 天
预期的帐户余额检索:每当发生交易时,平均每天一次。
您建议如何对此做出决定?非常感谢!
因此,就像我们的普通银行账户一样,我们有很多交易导致资金流入或流出。账户余额总是可以通过简单地总结交易价值来得出。在这种情况下,将更新的帐户余额存储在数据库中或在需要时重新计算它会更好吗?
每个账户的预期交易量:<5 天
预期的帐户余额检索:每当发生交易时,平均每天一次。
您建议如何对此做出决定?非常感谢!
前言
有一个客观事实:审计要求。此外,在处理公共资金时,必须遵守立法。
您不必实施完整的会计要求,您可以只实施您需要的部分。
相反,不建议实施标准会计要求(其中的部分)以外的其他东西,因为这可以保证当错误数量或负载超过某个阈值或系统扩展时,您将不得不重新实施. 可以并且因此应该避免的成本。
还需要说明:不要聘请不合格、未经认可的“审计师”。会有后果,就像你雇佣了一个不合格的开发者一样。如果税务局对你处以罚款,情况可能会更糟。
不那么原始的国家的标准会计方法是这样的。“最佳实践”,如果你愿意的话,在其他人身上。
此方法适用于任何具有类似操作的系统;需要;历史月度数据与当月要求,例如库存控制等。
首先,考虑因素。
永远不要重复数据。
如果可以导出当前余额(这里很简单),请不要将其与汇总列重复。这样的列是数据的重复。它打破了规范化规则。此外,它会创建一个更新异常,否则该异常是不存在的。
如果您确实使用汇总列,则每当更新事务时(如更改,而不是插入新事务时),汇总列值就会过时,因此无论如何都必须始终更新它。这就是更新异常的结果。这消除了拥有它的价值。
外部出版物。
分开点。如果余额被公布,如在每月银行对账单中,此类文件通常具有法律限制和影响,因此公布的当前余额值在公布后不得改变。
在发布日期之后,数据库中对外发布的数据的任何更改都是不诚实行为、欺诈等的证据。
您不希望您的银行在 2015 年 4 月更改他们在 2014 年 12 月的银行对账单中发布给您的当前余额。
该数字必须被视为审计数字,已发布且不可更改。
为了更正过去犯下的错误,现在正在更正,必要的更正或调整将作为当月的新交易进行(即使它适用于之前的某个月份或持续时间)。
这是因为适用的月份已关闭;经审计;并出版,因为历史已经发生并被记录下来,就无法改变历史。唯一有效的月份是当前月份。
对于不那么原始国家的计息系统等,当发现错误并具有历史影响时(例如,您在 2015 年 4 月发现,自 2015 年 12 月以来计算的证券利息不正确2014),今天计算更正的利息支付/扣除的价值,对于错误的天数,总和作为当月的交易插入。同样,唯一有效的月份是当前月份。
当然,证券的利率也必须更正,这样错误就不会重复。
如果您发现银行在计算您的储蓄(计息)账户的利息时出现错误,并且您已将其更正,您将在当月获得一笔构成整个调整值的存款。这是当月的交易。
银行不会: 改变历史;对每个历史月份应用利息;回忆历史银行对账单;重新发布具有历史意义的银行对账单。不,也许在第三世界国家除外。
相同的原则适用于库存控制系统。它保持理智。
所有真实的会计系统(即那些由适用国家的审计机关认可的系统,而不是比比皆是的米老鼠“包”)使用双重输入系统进行交易,正是因为它可以防止大量错误,其中最重要的是,资金不会“丢失”。这需要总账和复式记账。
此答案服务于所提出的问题,它不是复式记账法。
有关该主题的完整处理(详细数据模型;会计事务示例;受影响的行;和 SQL 代码示例),请参阅此问答:
复式记账的关系数据模型。
影响性能的主要问题超出了这个问题的范围,它们是在您是否实现真正的关系数据库(例如 1960 年代的记录归档系统,其特点是Record IDs
,部署在 SQL 数据库容器中)方便)。
无论表的数量如何,使用真正的关系键等都将保持高性能。
相反,RFS 将表现不佳,他们根本无法执行。在 RFS 的上下文中使用“规模”时,是一个欺诈性术语:它隐藏原因并试图解决除原因之外的所有问题。最重要的是,这样的系统没有关系完整性;关系力量;或关系系统的关系速度。
我所有的数据模型都在IDEF1X中呈现,这是自 1993 年以来用于建模关系数据库的标准。
我的IDEF1X 简介对于那些不熟悉关系模型或其建模方法的人来说是必不可少的读物。请注意,IDEF1X 模型具有丰富的细节和精确度,可以显示所有必需的细节,而国产模型则远少于此。这意味着,符号必须被理解。
对于每个帐户,表格中都会有一个ClosingBalance
, (每月 AccountStatement
一行),以及对帐单日期(通常是当月的第一天)和其他对帐单详细信息。AccountNo
这不是重复的,因为它是出于审计和理智目的而要求的。
对于库存,它是表QtyOnHand
中的一列(每月PartAudit
一行)PartCode
它有一个附加值,因为它将需要查询的 Transaction 行的范围限制在当月
同样,如果您的表是关系表,则主键AccountTransaction
将为 ( AccountNo
, Transaction DateTime
),它将以毫秒的速度检索事务。
而对于记录归档系统,“主键”将是TransactionID
,您将按交易日期检索当前月份,这可能会或可能不会正确索引,并且所需的行将分布在整个文件中。在任何情况下都远低于 ClusteredIndex 的速度,并且由于传播,它会导致 tablescan。
该AccountTransaction
表仍然很简单(银行账户交易的真实世界概念很简单)。它有一个正Amount
列。
对于每个Account
,CurrentBalance
是:
AccountStatement.ClosingBalance
上个月的日期,为方便起见,日期为下个月的第一天
(对于库存,PartAudit.QtyOnHand
)
加上AccountTransaction.Amounts
当月的 SUM,其中TransactionType
表示存款
(对于库存,PartMovement.Quantity
)
减去AccountTransaction.Amounts
当月的 SUM,其中 `MovementType 表示提款。
在这个方法AccountTransactions
中,只有当月的 处于不断变化的状态,因此必须推导出来。之前的所有月份都已发布并关闭,因此必须使用审计数字。
AccountTransaction
可以清除表中较旧的行。公共资金超过 10 年,否则为 5 年,业余俱乐部系统为 1 年。
当然,任何与会计系统相关的代码都必须使用真正的 OLTP 标准和真正的 SQL ACID 事务。
此设计包含所有范围级别的性能考虑(如果这不明显,请要求扩展)。数据库内部的扩展不是问题,任何仍然存在的扩展问题实际上都在数据库之外。
之所以需要说明这些项目,是因为许多 SO Answers 中提供了不正确的建议(当然,在民主的情况下,还得到了群众的支持),并且互联网上充斥着不正确的建议(业余爱好者喜欢发表他们的主观“真相”):
显然,有些人不明白我给出了一个技术术语的方法,用于针对清晰的数据模型进行操作。因此,它不是特定国家/地区特定应用程序的伪代码。该方法是为有能力的开发人员准备的,对于需要手把手的人来说不够详细。
他们也不明白一个月的截止时间是一个例子:如果您的税务局的截止日期是季度,那么请务必使用季度截止;如果您唯一的法律要求是年度,请使用年度。
即使您的截止日期是出于外部或合规目的,公司也可能会选择每月截止日期,用于内部审计和理智目的(即,将流动状态的时间长度保持在最低限度) .
例如。在澳大利亚,税务局对企业的截止日期是每季度一次,但较大的公司每月都会取消他们的库存控制(这样可以避免长期追逐错误)。
例如。银行每月都有法律合规要求,因此他们每月对数据进行内部审计并结账。
在原始国家和流氓国家,银行出于明显的邪恶目的将其状态流动期保持在最大值。他们中的一些人每年只制作合规报告。这就是为什么澳大利亚的银行不会倒闭的原因之一。
在AccountTransaction
表格中,不要在金额列中使用负数/正数。钱总是有正值的,没有负二十美元(或者你欠我的负五十美元)这样的东西,然后计算出双重否定意味着别的东西。
移动方向,或者你将如何处理资金,是一个独立且离散的事实(对于AccountTransaction.Amount
)。这需要一个单独的列(一个数据中的两个事实违反了规范化规则,其结果是它在代码中引入了复杂性)。
实现一个TransactionType
参考表,它的主键是(D, W
)作为您的存款/取款的起点。随着系统的增长,只需A, a, F, w
为调整信用添加();调整借方;银行费用; ATM_取款;等等
无需更改代码。
在一些原始国家,诉讼要求规定,在任何列出交易的报告中,每一行都必须显示累计。(请注意,这不是审计要求,因为这些要求优于[(参见上述方法)法院要求;审计师比律师更愚蠢;等等。)
显然,我不会反对法院的要求。问题是原始编码器将其翻译成:哦,哦,我们必须实现一个 AccountTransaction.CurrentBalance
列。他们不明白:
在报表上打印列的要求并不是在数据库中存储值的要求
任何类型的运行总计都是派生值,并且很容易编码(如果对您来说不容易,请提出问题)。只需在报告中实现所需的代码。
实施运行总计,例如。AccountTransaction.CurrentBalance
作为一个列会导致可怕的问题:
引入了重复列,因为它是可导出的。打破规范化。引入更新异常。
更新异常:每当历史上插入事务或AccountTransaction.Amount
更改事务时,必须重新计算和更新AccountTransaction.CurrentBalances
从该日期到现在的所有事务。
在上述案例中,提交给法庭使用的报告现在已经过时(每份在线数据报告在打印时都已过时)。IE。打印; 审查; 更改交易;重印; 重新审查,直到您满意为止。在任何情况下都是没有意义的。
这就是为什么在不那么原始的国家,法院不接受任何旧的印刷文件,他们只接受公布的数字,例如。银行对账单,已经符合审计要求(参考上述方法),并且不能被召回或更改和重新打印。
亚历克斯:
是的,代码很好看,谢谢。甚至可能是一个样本“桶店”,这样人们就可以永远看到起始模式,这会让世界变得更好。
对于上面的数据模型。
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 派生。您想要易于编码,但您愿意在两个地方进行编码,而不是一个。这正是那种容易出错的代码。
根据 EF Codd 博士的关系模型完全规范化的数据库提供了最简单、最合乎逻辑、最直接的代码。(在我的工作中,我通过合同保证每份报告都可以由一个SELECT
.) 提供服务。)
ENUM
不是 SQL。(免费软件 NONsql 套件不符合 SQL 标准,但它们确实具有 SQL 中不需要的额外功能。)如果您的应用程序升级到商业 SQL 平台,您将不得不将所有这些重新编写ENUMs
为普通的查找表。以aCHAR(1)
或aINT
为PK。然后你会意识到它实际上是一个带有 PK 的表。
错误的值为零(它也有负面影响)。真理的价值是一。我不会以一换零。因此,这不是一个权衡。这只是您的开发决定。
这是相当主观的。我建议考虑的事情是:
就所提出的两种方法的优点而言,按需求和交易价值可能是更容易/更快实施的方法。
但是,它不会像数据库中的字段一样扩展和维护当前帐户余额并随时更新它。它会在一定程度上增加您的整体交易处理时间,因为每笔交易都需要运行查询来计算当前账户余额,然后才能继续进行。在实践中,这些可能是小问题,除非您有大量的账户/交易或期望在不久的将来。
第二种方法的缺点是最初设置可能需要更多的开发时间/精力,并且可能需要您考虑如何在帐户内同步交易以确保每个人都能准确地看到和更新余额每时每刻。
因此,这主要取决于项目的需求是什么,目前最好将开发时间花在哪里,以及是否值得现在对解决方案进行面向未来的验证,而不是稍后实施第二种方法,当性能和可扩展性成为现实时,而不是比理论,问题。