【问题标题】:SQL monthly rolling sumSQL 每月滚动总和
【发布时间】:2019-07-13 19:28:55
【问题描述】:

我正在尝试从以下包含交易的 postgresql 表中计算银行账户的每月余额:

# \d transactions
                 View "public.transactions"
 Column |       Type       | Collation | Nullable | Default 
--------+------------------+-----------+----------+---------
 year   | double precision |           |          | 
 month  | double precision |           |          | 
 bank   | text             |           |          | 
 amount | numeric          |           |          | 

在“滚动总和”中,我的意思是总和应该包含从开始时间到给定月份结束的所有交易的总和,而不仅仅是给定月份中的所有交易。

我想出了以下查询:

select 
    a.year, a.month, a.bank, 
    (select sum(b.amount) from transactions b 
     where b.year < a.year 
        or (b.year = a.year and b.month <= a.month)) 
from 
    transactions a 
order by 
    bank, year, month;

问题在于,每个银行每个月的行数与那里的交易数一样多。如果更多,那么更多,如果没有,那么没有。 我想要一个查询,其中每个银行和月份在整个时间间隔(包括第一笔和最后一笔交易)中恰好包含一行。

怎么做?

可以在https://rextester.com/WJP53830 找到示例数据集和查询,由@a_horse_with_no_name 提供

【问题讨论】:

  • 你也可以标记正在使用的 dbms 吗?
  • 样本数据和期望的结果真的很有帮助。 所说的“月结余”是什么意思? 所说的“滚动”是什么意思?数据中如何表示“银行账户”?
  • 大多数数据库都提供了一个窗口函数来计算累积和。但是使用它的细节很大程度上取决于您使用的 DBMS。您应该始终在涉及 SQL 的问题中包含特定的数据库技术。
  • 不相关,但是:yearmonth 最好存储为 integer

标签: sql postgresql


【解决方案1】:

您需要先生成月份列表,然后您可以将您的交易表外部连接到该列表。

with all_years as (
  select y.year, m.month, b.bank
  from generate_series(2010, 2019) as y(year) --<< adjust here for your desired range of years
    cross join generate_series(1,12) as m(month)
    cross join (select distinct bank from transactions) as b(bank)
)
select ay.*, sum(amount) over (partition by ay.bank order by ay.year, ay.month)
from all_years ay
  left join transactions t on (ay.year, ay.month, ay.bank) = (t.year::int, t.month::int, t.bank)
order by bank, year, month;  

与所有银行的交叉连接是必要的,这样all_years CTE 也将包含每个月行的银行。

在线示例:https://rextester.com/ZZBVM16426

【讨论】:

  • 如果一个月内有多个交易,它仍然有更多的行。请参阅 2018 年 4 月rextester.com/WJP53830
  • @ÁrpádMagosányi: 那么你需要添加一个group by
【解决方案2】:

这是我在 Oracle 10 SQL 中的建议:

select a.year,a.month,a.bank, (select sum(b.amount) from 
(select a.year as year,a.month as month,a.bank as bank,
 sum(a.amount) as amount from transactions c
 group by a.year,a.month,a.bank
) b
where b.year<a.year or (b.year=a.year and b.month<=a.month)) 
from transactions a order by bank, year, month;

【讨论】:

  • OP 的标题表示 滚动总和 而不是简单的总和。给定 CLI 命令\d 和默认模式"public",RDBMS 很可能是 Postgres。
  • 寻找滚动总和的精确定义会很有帮助。
  • 编辑了我的答案以计算滚动总和而不是总和(语义未与真实数据库一起检查;可能速度可提高)
  • 错误:列“a.year”必须出现在 GROUP BY 子句中或用于聚合函数第 1 行:选择 a.year,a.month,a.bank, (select sum( b.amount) 来自
【解决方案3】:

考虑首先按银行和月份汇总所有交易,然后运行一个窗口 SUM() OVER() 以从最早金额开始滚动每月总和。

WITH agg AS (
   SELECT t.year, t.month, t.bank, SUM(t.amount) AS Sum_Amount
   FROM transactions t
   GROUP BY t.year, t.month, t.bank
)

SELECT agg.year, agg.month, agg.bank,
       SUM(agg.Sum_Amount) OVER (PARTITION BY agg.bank ORDER BY agg.year, agg.month) AS rolling_sum
FROM agg
ORDER BY agg.year, agg.month, agg.bank

如果您想要 YTD 滚动总和,请通过将年份添加到分区来调整 OVER() 子句:

SUM(agg.Sum_Amount) OVER (PARTITION BY agg.bank, agg.year ORDER BY agg.month)

【讨论】:

  • 谢谢,但它不会为您提供那些不包含给定银行交易的月份的数据。
  • 然后创建一个包含所有年份和月份的日历表,然后将其加入到这个结果集中。
猜你喜欢
  • 2022-08-02
  • 1970-01-01
  • 2021-02-11
  • 2021-08-21
  • 2020-02-11
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 1970-01-01
相关资源
最近更新 更多