数据库设计:计算帐户余额


76

如何设计数据库以计算帐户余额?

1)目前,我从交易表中计算帐户余额。在我的交易表中,我有“说明”和“金额”等。

然后,我将所有“金额”值相加,得出用户的帐户余额。


我向我的朋友展示了这个,他说这不是一个好的解决方案,当我的数据库增长缓慢时?他说我应该创建一个单独的表来存储计算出的帐户余额。如果这样做,我将不得不维护两个表,这很冒险,帐户余额表可能会不同步。

有什么建议吗?

编辑:选项2:我应该在我的交易表“余额”中添加一个额外的列。现在,我不需要遍历很多数据来执行计算。

示例John购买了$ 100的信用额,他负债了$ 60,然后又增加了$ 200的信用额。

金额$ 100,余额$ 100。

金额-$ 60,余额$ 40。

金额$ 200,余额$ 240。


1
您预期的交易量是多少?
Patrick Honorez 2010年

wtf是每个人都得到负分的问题?
Patrick Honorez 2010年

不知道iDevelop,我没有给任何人一个积极或消极的观点:),现在我很困惑,一个人说是,另一个人说不。到底是怎么回事!??
001

7
Transactions表中“余额”字段的问题在于,您不仅在行级上构建了一个传递性依赖项(不是“ Normal”),还在列级中添加了一个传递性依赖项,如果您遇到问题,这将是令人头疼的(触发失败或其他)。我的建议是写下规范化的结构,然后编写您打算拥有的每个“用例”,与他人讨论,然后根据用例查看您的结构,以查看是否需要进行非规范化。无论如何,设计阶段至关重要,在这一点上花费您的时间并不是在“浪费”时间!
Patrick Honorez 2010年

6
“从前几年从未删除过交易” ...对此我要说两遍。您可能会考虑在一段时间后将旧事务移至存档表,+在活动表中创建特殊类型的事务(initialBalance)。这可能是年度流程(或任何适当的时间范围)的一部分。您应该在“用例”中包括这一点;-)
Patrick Honorez,2010年

Answers:


72

一个古老的问题,从来没有优雅地解决过。

我使用过的所有银行业务套餐都将余额存储在帐户实体中。不可思议地根据运动历史进行计算。

正确的方法是:

  • 移动表为每个帐户都有一个“期初余额”交易。当您需要将旧机芯从活动机芯表中移出到历史表中时,将需要几年的时间。
  • 帐户实体具有余额字段
  • 变动表上有一个触发器,用于更新贷记和借记帐户的帐户余额。显然,它具有承诺控制。如果您没有触发器,那么就需要一个唯一的模块来在承诺控制下编写动作
  • 您有一个可以脱机运行的“安全网”程序,该程序将重新计算所有余额并显示(并可以更正)错误的余额。这对于测试非常有用。

某些系统将所有移动存储为正数,并通过反转from / to字段或带有标记来表示贷方/借方。就个人而言,我更喜欢贷方字段,借方字段和签名金额,这使冲销更容易遵循。

请注意,这些方法适用于现金和证券。

证券交易可能非常棘手,尤其是对于公司行为而言,您将需要容纳一项交易,以更新一个或多个买方和卖方的现金余额,其安全头寸余额以及可能的经纪人/存储库。


4
“就我个人而言,我更喜欢一个贷方字段,一个借方字段和一个已签名的金额,这使冲销更容易遵循。”为什么不只在符号中包含一个“金额”字段呢?
001

9
@ 001每个事务包含3个字段。1 /借方帐户的ID。2 /帐户的ID。3 /金额,正=从债权人转移到借方,负=从债务人转移到债权人(通常是冲销)。我建议您阅读en.wikipedia.org/wiki/Double-entry_bookkeeping_system
smirkingman 2010年

1
@ 001这是2个动作。1 / X将5000贷记到Y。2 / X向Z支付500税。必须这样做,以后,有人会问“显示对税帐户Z的所有付款”,而您不希望将5000计入那个答案。我真的建议您对簿记进行一些背景阅读,您会发现它非常有用
smirkingman 2010年

1
@ 001如上所述,您应该学习复式记帐的基础知识
smirkingman 2010年

6
这个答案是不正确的,与代码有关的并且麻烦。一种方法是:(a)符合审计要求和立法,(b)中要求触发器; 离线安全网;重复的栏;重复的数据,并且(c)不管表的填充量如何,效果都很好,请查看此答案
PerformanceDBA

4

您应该存储当前帐户余额,并始终保持最新状态。交易表只是过去发生的事情的记录,不应仅仅为了获取当前余额而频繁使用。考虑到许多查询不只是想要余额,它们还希望对它们进行筛选,排序和分组,等等。将您在复杂查询中间创建的每个交易相加会降低性能,即使数据库规模较小也是如此。

对这对表的所有更新都应在事务中,并应确保所有内容保持同步(并且帐户绝不会透支超过其限制)或事务回滚。作为一项额外措施,您可以运行审核查询,以定期检查此情况。


告诉有人来审核您的簿记。他们会笑多久,您会感到惊讶。每个帐户的每笔交易都必须编号(以维持订单),您可以在其中放置新的帐户余额。实际上不需要第二张桌子。没有性能损失。顺便说一句,簿记是如何完成的。这就是全部。
TomTom 2010年

@TomTom:现在我明白了。你的主意很好。太糟糕了,您的答案中没有明确说出来,太糟糕了,您的答案看起来太好斗了!
Patrick Honorez 2010年

1
@ TomTom,Marcelo的措词有点含糊,但是您对审核的评论没有帮助,也不对。审计与确定正确性有关,与速度优化无关。Marcelo有点不精确,因为他谈论的是“当前余额”,实际上,大多数金融系统都会根据帐户和其他分析维度(由某些日期粒度汇总)来保留余额。对于需要在除符合预订顺序的属性之外的任何内容上筛选交易的任何报表,您保持交易水平平衡的想法没有用。
不合理

4

这是我只有一个表的数据库设计,仅用于存储操作/事务的历史记录。目前在许多小型项目中发挥作用。

这不会替代特定的设计。这是一个通用的解决方案,可以适合大多数应用程序。

id:int标准行ID

operation_type:int操作类型。支付,收取,利息等

source_type:int从此处进行操作。目标表或类别:用户,银行,提供者等

source_id:int数据库中源的ID

target_type:int要执行的操作。目标表或类别:用户,银行,提供者等

target_id:int数据库中目标的ID

金额:十进制(19,2签名)价格值正或负,按总和

account_balance:小数(19,2个已签名)结果余额

extra_value_a:decimal(19,2signed)[这是不使用字符串存储的最通用的选项],您可以存储一个附加数字:利息百分比,折扣,减免等。

created_at:时间戳

对于source_type和target_type,最好使用枚举或表appart。

如果需要特定的余额,则可以查询按created_at降序限制为1排序的最后一个操作。可以按源,目标,operation_type等查询。

为了获得更好的性能,建议将当前余额存储在所需的目标对象中。


2

解决此问题的常见方法是在快照架构中维持(例如)每月的期初余额。可以通过将当月的交易数据添加到月初余额中来计算当前余额。此方法通常用于帐户套餐中,尤其是在您可能需要进行货币换算和重估的地方。

如果数据量有问题,可以将较早的余额存档。

另外,如果您在系统上没有专用的外部数据仓库或管理报告工具,则余额对报告很有用。



0

您的朋友是错误的,您是正确的,我建议您现在不要改变。
如果您的数据库因此而变慢,并且在您验证了所有其余部分(正确的索引编制)之后,可能需要使用一些非规范化。
然后,您可以在Accounts表中放置BalanceAtStartOfYear字段,并仅汇总今年的记录(或任何类似的方法)。
但是我当然不建议您提前使用此方法。


啊...不,认真。会计是一项棘手的事情,可能需要满足一些严格的法律要求。临时汇总不会减少它。
TomTom

1
@TomTom。当然,如果交易是根据会计准则实施的(您不是),那么它们确实会这样做,在这种情况下,“ ad hoc”是错误的。此外,在每行中复制CurrentBalance是很愚蠢的:当需要进行调整时,必须更新大量行。
PerformanceDBA

0

这里想向您建议如何用一种非常简单的方式存储您的期初余额:-

  1. 在事务表上创建一个触发函数,仅在更新或插入后才能调用。

  2. 在帐户主表中创建一个名称为“期初余额”的列。

  3. 将您的期初余额保存在主表的期初余额列中的数组中。

  4. 您甚至不需要使用服务器端语言就可以使用此存储数组,只需使用PostgreSQL中可用的数据库数组函数即可。

  5. 当您要重新计算数组中的期初余额时,只需将事务表与数组函数分组并更新主表中的整个数据即可。

我已经在PostgreSQL中做到了,并且工作正常。

在事务表变得很重的时间段内,您可以根据日期对事务表进行分区,以提高性能。这种方法非常简单,并且不需要使用任何多余的表,如果连接表,这会降低性能,因为在连接中使用较少的表将为您带来高性能。


嗨,我不清楚。“期初余额”是在同一交易表中还是在单独的表中?
Axil

0

我的方法是将借方存储在借方列中,将贷方存储在贷方列中,并在获取数据时创建两个数组:借方和贷方数组。然后继续将选定的数据追加到数组,并为python执行此操作:

def real_insert(arr, index, value):
    try:
        arr[index] = value
    except IndexError:
        arr.insert(index, value)


def add_array(args=[], index=0):
    total = 0
    if index:
        for a in args[: index]:
            total += a
    else:
        for a in args:
            total += a
    return total

然后

for n in range(0, len(array), 1):
    self.store.clear()
    self.store.append([str(array[n][4])])
    real_insert(self.row_id, n, array[n][0])
    real_insert(self.debit_array, n, array[n][7])
    real_insert(self.credit_array, n, array[n][8])
    if self.category in ["Assets", "Expenses"]:
        balance = add_array(self.debit_array) - add_array(self.credit_array)
    else:
        balance = add_array(self.credit_array) - add_array(self.debit_array)

-3

简单的答案:全部三个。

存储当前余额;并且在每个交易中存储该时间点的动向和当前余额的快照。这会给额外的东西审计好处。

我从未从事过核心银行系统的工作,但是我曾从事过投资管理系统的工作,以我的经验,这就是它的完成方式。


1
这给审核带来了额外的好处。这有助于审核软件(根据正确的计算和检查交易是否被删除;不是必须的=唯一的方式),这是我审核工作的一部分;但这与会计审计没有直接关系(IT审计只是财务审计的一部分)。
不合理

正确,但是在大型互操作系统中,某些进程(例如,通宵的批处理)产生异常的情况并非不可能。一点非规范化,将有助于确保可以对其进行跟踪。
狗耳

@Dog Ears,我知道-所以您的大部分输入来自数据交换/互操作,因此IT审计是财务审计更重要的组成部分。有趣。我仍然保持我的立场:这是一个额外的控制价值,将存在于所有交易中。如果我想审核数据交换过程,我会把精力集中在它上面(使用某种形式的数据交换校验和:由发起方计算并简单接收,以接收格式对接收到的数据进行计算-检查传输的完整性,根据导入的数据进行计算-检查您的导入程序)
不合理

1
@smirkingman为了社区的利益,我的建议有多糟糕,与您的建议有何不同?似乎我们基本上建议了同一件事?
Dog Ears 2010年

3
@dogs耳朵的可怕之处在于每一个动作都在平衡。出现问题时,会有更多重复的数据和噩梦解决。
smirkingman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.