从余额行中递归减去存款
Posted
技术标签:
【中文标题】从余额行中递归减去存款【英文标题】:Recursive subtraction of deposit from balance rows 【发布时间】:2014-11-26 07:39:16 【问题描述】:考虑以下 2 个表格:
表 A:
PIN | ENCOUNTER | BALANCE | REFERENCE_DATE
------------------------------------------
P1 | ABC | 100 | 11-19-2014
P1 | HJI | 300 | 11-20-2014
P1 | PIY | 700 | 11-21-2014
P2 | CDO | 200 | 11-20-2014
P2 | NHG | 200 | 11-21-2014
P3 | CVB | 500 | 11-20-2014
P3 | SJK | 100 | 11-21-2014
表 B:
PIN | DEPOSIT
-------------
P1 | 1000
P2 | 400
P3 | 100
最初表B
的DEPOSIT
值将从表A
中的BALANCE
中减去,最早的REFERENCE_DATE
与PIN
匹配。如果差值大于 0,则会从下一行的 BALANCE
中减去,直到剩余的 DEPOSIT
小于或等于 0。
从余额中减去存款后的结果如下所示。我已经包括了另一列,其中存款是按遭遇划分的:
PIN | ENCOUNTER | BALANCE | REFERENCE_DATE | DEPOSITS_BREAKDOWN
---------------------------------------------------------------
P1 | ABC | 0 | 11-19-2014 | 100
P1 | HJI | 0 | 11-20-2014 | 300
P1 | PIY | 100 | 11-21-2014 | 600
P2 | CDO | 0 | 11-20-2014 | 200
P2 | NHG | 0 | 11-21-2014 | 200
P3 | CVB | 400 | 11-20-2014 | 100
P3 | SJK | 100 | 11-21-2014 | 0
我的 Postgres 版本是 9.3。我正在努力为这个制定查询。
【问题讨论】:
最后的结果行应该是 500,而不是 100。 @ErwinBrandstetter,如果是这样,那我就浪费了 30 分钟。这个100
有点难对付。
其实最后一行真的是100,分配给A表P3行的100押金已经减去了遇到CVB的那一行。
非常感谢先生。但是是否也可以在另一列中列出存款被分割了多少?我已经更新了结果表。
是的,这很便宜。另外:您是否知道没有@-reply 没有人收到您的评论通知?偶然发现的。
【参考方案1】:
在DEPOSIT
覆盖BALANCE
时设置为0
正如您所澄清的,您不想要 BALANCE
的运行总和,只需设置为 0 直到 DEPOSIT
用完:
SELECT PIN, ENCOUNTER
, CASE WHEN last_sum >= DEPOSIT THEN BALANCE
ELSE GREATEST (last_sum + BALANCE - DEPOSIT, 0) END AS BALANCE
, REFERENCE_DATE
, CASE WHEN last_sum >= DEPOSIT THEN 0
ELSE LEAST (BALANCE, DEPOSIT - last_sum) END AS DEPOSITS_BREAKDOWN
FROM (
SELECT a.*
, COALESCE(sum(a.BALANCE) OVER (
PARTITION BY PIN ORDER BY a.REFERENCE_DATE
ROWS BETWEEN UNBOUNDED PRECEDING
AND 1 PRECEDING), 0) AS last_sum
, COALESCE(b.DEPOSIT, 0) AS DEPOSIT
FROM table_a a
LEFT JOIN table_b b USING (pin)
) sub;
准确返回您想要的结果。
SQL Fiddle.
我采纳了@vyegorov 所评论的更简单连接的想法。
LEFT JOIN
到 table_b
- 这样就有可能在 table_b
中找不到任何行。
在子查询中,计算 BALANCE
的运行总和,直到最后一行 (last_sum
)。为此在窗口函数中使用自定义框架。并且 COALESCE
默认为 0 没有行。相关答案以及自定义框架的更多解释:
在最终的SELECT
中,如果last_sum
等于或大于DEPOSIT
(已用完),则返回原始BALANCE
。 ELSE 返回剩余差或 0 是 BALANCE
(last_sum + BALANCE
) 的运行总和小于 DEPOSIT
。
运行总和
上一个(更简单的)答案以 BALANCE
作为运行总和(最后一行 500 而不是 100):
SELECT a.PIN, a.ENCOUNTER
, GREATEST(sum(a.BALANCE) OVER (PARTITION BY PIN ORDER BY a.REFERENCE_DATE)
- COALESCE(b.DEPOSIT, 0), 0) AS BALANCE
, a.REFERENCE_DATE
FROM table_a a
LEFT JOIN table_b b USING (pin);
【讨论】:
【参考方案2】:我想出了这个查询:
SELECT *,
sum(balance) OVER w balance_accum,
greatest(deposit - sum(balance) OVER w, 0) deposit_new,
greatest(sum(balance) OVER w - deposit, 0) balance_new
FROM table_a JOIN table_b USING(pin)
WINDOW w AS (PARTITION BY pin ORDER BY reference_date)
ORDER BY pin, reference_date;
SQL Fiddle
正如 Erwin 提到的,这一行假设最后一行包含 500
而不是 100
。
编辑:
这个查询会产生所需的输出:
SELECT s.*,
CASE WHEN min(deposit_new) OVER w = 0 THEN 0
ELSE least(min(deposit_new) OVER w, deposit_diff) END deposit_used,
balance -
CASE WHEN min(deposit_new) OVER w = 0 THEN 0
ELSE least(min(deposit_new) OVER w, deposit_diff) END balance_real
FROM
(
SELECT *,
sum(balance) OVER w balance_accum,
greatest(coalesce(deposit,0) - sum(balance) OVER w, 0) deposit_new,
least(balance, coalesce(deposit,0)) deposit_diff
FROM table_a LEFT JOIN table_b USING(pin)
WINDOW w AS (PARTITION BY pin ORDER BY reference_date)
) s
WINDOW w AS (PARTITION BY pin ORDER BY reference_date
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
ORDER BY pin, reference_date;
这里有什么(子查询):
正如 Erwin 建议的那样,LEFT JOIN
和 coalesce(deposit,0)
用于保存没有存款的条目;
实际balance
值的总和计算并从deposit
中减去(输出中的列deposit_new
);
deposit_diff
是balance
和deposit
中的最小值,用于调整外部平衡。
在外面:
检查最小deposit_new
值,如果达到0
,则跳过所有进一步的“使用”;
否则至少取deposit_new
和deposit_diff
的值;
检查组中所有前面的行。
子查询是必需的,因为我必须在逻辑中使用窗口函数的结果。
SQL Fiddle 2
【讨论】:
加入 table_a 中的每一行是个好主意。少一个窗口函数的子查询。以上是关于从余额行中递归减去存款的主要内容,如果未能解决你的问题,请参考以下文章
编写一个类似银行账户的程序,属性:账号 储户姓名 地址 存款余额 利率。方法:存款 取款查询余额计算利息
JAVA编写模拟ATM机进行帐户余额查询 实现存款和取款业务(使用带参数的方法)
首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能。