【问题标题】:Account balance: how to calculate it properly on SQL账户余额:如何在 SQL 上正确计算
【发布时间】:2014-10-21 01:45:35
【问题描述】:

我正在开发一个应用程序,我必须在其中存储一些银行账户信息,包括每日账户余额。

例如:

17/10/2014 - (+) - Starting/Initial balance - 5,000.00
17/10/2014 - (=) - Balance - 5,000.00
-
18/10/2014 - (-) - Payment - (1,000.00)
18/10/2014 - (=) - Balance - 4,000.00
-
19/10/2014 - (=) - Balance - 4,000.00
-
20/10/2014 - (-) - Payment - (1,000.00)
20/10/2014 - (=) - Balance - 3,000.00

我想我可以创建一个特定的“account_balance”表,我可以在其中存储每天的每个帐户余额。

如果我错了,你能帮我找到最好的方法吗?但是,如果我是对的,如何让数据库计算每日余额,特别是当用户开始编辑旧值时如何让数据库更新余额?

我所说的“旧价值观”是指:

1 - 这是“帐户 A”语句的样子:

18/10/2014 - (+) - Starting/Initial balance - 5,000.00
18/10/2014 - (=) - Balance - 5,000.00
-
19/10/2014 - (=) - Balance - 5,000.00
-
20/10/2014 - (=) - Balance - 5,000.00

2 - 但是用户忘记注册收入,所以他通过添加新收入来注册(所以现在必须更新余额):

18/10/2014 - (+) - Starting/Initial balance - 5,000.00
18/10/2014 - (+) - Sales commission - 2,500.00 <- USER ADDED THIS.
18/10/2014 - (=) - Balance - 7,500.00 <- THIS BALANCE HAS BEEN UPDATED.
-
19/10/2014 - (=) - Balance - 7,500.00 <- THIS BALANCE HAS BEEN UPDATED.
-
20/10/2014 - (=) - Balance - 7,500.00 <- THIS BALANCE HAS BEEN UPDATED.

【问题讨论】:

  • 只存储交易并使用视图显示实际余额怎么样?
  • @RADAR 我来看看。谢谢!
  • @EricB.,你能详细说明一下吗?我从更大的数据库结构开始。
  • 你真的需要存储每日账户余额吗? (某些应用程序确实可以;大多数应用程序确实没有。)在您允许编辑值之前,请三思而后行。如果您允许编辑,您的会计师可能会追捕您并用锋利的棍子戳您的眼睛。它们用于插入补偿事务,而不是编辑错误事务。

标签: mysql sql database postgresql balance


【解决方案1】:

不要存储余额,而是有一个表来存储每个用户的交易。

例如:

Date            Transactions        Comment
17/10/2014      +5,000.00           Starting/Initial balance - 
18/10/2014      -1,000.00           Payment
20/10/2014      -1,000.00           Payment

然后你可以创建一个平衡视图(类似):

create view balance as
  select userId, sum(transactions) as balance from TransactionTable group by userId

如果您想更精确并包括开始和停止日期(即:能够在任何时间点获得余额),您可以创建一个parametrized view(没有尝试使用日期,但我假设它也可以工作)。

【讨论】:

  • 对。但是服务器资源呢?通过这种方法,每次用户输入一些新的过滤日期时,服务器都必须计算余额。如果我有按天存储的余额,它只会恢复该数据。但是......好吧,我不是一个有经验的用户。那么您对此有何看法?
  • 一个有效的考虑。正确的索引会有很大帮助,但 MySQL 仍然需要对所有行求和。很大程度上取决于您希望每个用户每天进行的交易数量,以及您需要多久检查一次每日余额。但鉴于您希望能够更新以前的记录,这种视图将确保数据始终准确。
【解决方案2】:

此答案适用于 PostgreSQL,OP 在 cmets 中询问原始问题。

数据完整性对我来说是最重要的。所以我不愿意存储聚合值,除非 a) 性能很差,b) dbms 可以保证聚合值是正确的。

我从这张桌子开始。

create table transactions (
  trans_id serial primary key,
  cust_id integer not null, -- foreign key, references customers, not shown
  trans_time timestamp not null default current_timestamp,
  trans_amt numeric(14,2) not null 
);

create index on transactions (cust_id);

我选择了时间戳而不是日期,因为像这样的应用程序通常需要支持时间戳,而且在一般情况下,它的性能应该比日期差。如果我们在时间戳方面获得良好的性能,我们应该能够在日期方面获得良好的性能。我没有假设单个客户的时间戳是唯一的。

我将 2000 万行随机数据加载到此表中,然后我更新了统计信息。数据包括正数和负数。为了方便目视检查,金额甚至达到数百美元。

此类应用程序中更常见的查询之一涉及为单个客户返回一个登记册——所有交易都有流动余额。

这是客户 128 前三天的原始数据。

cust_id trans_time trans_amt -- 128 2014-01-01 08:36:09 200.00 128 2014-01-01 14:18:10 200.00 128 2014-01-01 14:26:56 0.00 128 2014-01-01 18:17:31 400.00 128 2014-01-01 20:18:53 100.00 128 2014-01-02 00:10:35 0.00 128 2014-01-02 01:44:26 300.00 128 2014-01-02 15:49:31 -300.00 128 2014-01-03 00:33:23 400.00 128 2014-01-03 11:55:13 -200.00 128 2014-01-03 11:56:34 -100.00 128 2014-01-03 14:58:42 -400.00 128 2014-01-03 17:31:11 0.00

我们应该预计前三天的这些金额。

2014-01-01 900.00 2014-01-02 0.00 2014-01-03 -300.00

前三天的运行余额应该是这样的。

2014-01-01 900.00 2014-01-02 900.00 2014-01-03 600.00

每日余额记录

select 
      cust_id
    , trans_date
    , sum(daily_amt) over (partition by cust_id order by trans_date) daily_balance
from (select 
            cust_id
          , trans_time::date trans_date
          , sum(trans_amt) daily_amt
      from transactions
      where cust_id = 128
      group by cust_id, trans_date) x
order by cust_id, trans_date;
cust_id trans_date daily_balance -- 128 2014-01-01 900.00 128 2014-01-02 900.00 128 2014-01-03 600.00 . . .

寄存器执行计划

执行计划显示上面的查询在 12 毫秒内运行。我认为这对于这种应用程序来说是合理的,但我可以通过索引表达式 (trans_time::date) 或通过复合索引将运行时间减少到 12 毫秒以下。

“WindowAgg(成本=7232.14..7252.94 行=1040 宽度=40)(实际时间=11.728..12.093 行=294 循环=1)” “ -> 排序(成本=7232.14..7234.74 行=1040 宽度=40)(实际时间=11.700..11.733 行=294 循环=1)” " 排序键:transactions.cust_id, ((transactions.trans_time)::date)" “排序方法:快速排序内存:38kB” “ -> HashAggregate(成本=7156.62..7169.62 行=1040 宽度=16)(实际时间=11.392..11.466 行=294 循环=1)” “ -> 事务上的位图堆扫描(成本=39.66..7141.89 行=1964 宽度=16)(实际时间=0.839..9.753 行=1961 循环=1)” “重新检查条件:(cust_id = 128)” “ -> transactions_cust_id_idx 上的位图索引扫描(成本=0.00..39.17 行=1964 宽度=0)(实际时间=0.501..0.501 行=1961 循环=1)” “索引条件:(cust_id = 128)” “总运行时间:12.272 毫秒”

【讨论】:

  • 感谢您的出色回答和解释!但是我可以用 MySQL 做吗?会有什么大的不同吗?
  • MySQL 不支持窗口函数或窗口聚合,因此您必须在 MySQL 中以不同的方式进行操作。性能会受到影响,但可能仍然足够快。如果我有时间,我会为 MySQL 重写它。
  • Mike,根据您的建议(使用 PostgreSQL),我阅读了一些关于两个数据库的差异和用例的文章,PostgreSQL 似乎是一个不错的选择。无论如何,MySQL 方法会很棒! PostgreSQL 有类似 MySQL Workbench 的东西吗?我在 Debian 上,但在存储库中没有找到任何东西。
  • 看看 pgAdminIII。 可能在安装 PostgreSQL 时默认安装。
【解决方案3】:

在我的例子中,下面是表架构

为此我提供以下解决方案

SELECT id, user_id, credit, debit,
COALESCE(((SELECT SUM(credit) FROM user_transactions b WHERE b.id <= a.id AND user_id = '7') - (SELECT SUM(debit) FROM user_transactions b WHERE b.id <= a.id AND user_id = '7')), 0) as balance
FROM user_transactions a WHERE user_id = '7' ORDER BY id ASC;

这是结果希望它会帮助你。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-21
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多