将两个表聚合成时间序列

Posted

技术标签:

【中文标题】将两个表聚合成时间序列【英文标题】:Aggregating two tables into time series 【发布时间】:2020-06-05 22:48:26 【问题描述】:

假设我有两个表,它们成对地保存面向时间的数据(日期、数据)。其中一个存放我的日常开支,另一个存放我的日常收入。日期表示该费用/收入率的开始时间。

例如:如果我在收入表中有两条记录:(2020-01-01, 50), (2020-02-14, 100) 表示在 2020-01-01 和 2020-02-14 之间我的收入是每天 50 个单位,2020-02-14 之后的收入是每天 100 个单位。费用也是如此。

我想在 postgres 表中为给定时间间隔生成一个时间序列(可能使用 time generate_series(date,date,interval) 函数),其中一行如下所示: (天、收入、费用)

例如,如果我的收入表如下所示:


+------------+--------+
|    date    | income |
+------------+--------+
| 2020-02-12 |     50 |
| 2020-02-14 |    100 |
+------------+--------+

还有这样的费用:

+------------+--------------+
|    date    | expenses     |
+------------+--------------+
| 2020-02-12 |           70 |
| 2020-02-13 |           50 |
+------------+--------------+

我希望从 2020-02-12 到 2020-02-15 间隔的结果如下所示:

+------------+--------+--------------+
|    day     | income | expenses     |
+------------+--------+--------------+
| 2020-02-12 |     50 |           70 |
| 2020-02-13 |     50 |           50 |
| 2020-02-14 |    100 |           50 |
| 2020-02-15 |    100 |           50 |
+------------+--------+--------------+

以便稍后我可以计算我的利润、损失和其他统计数据。我怎样才能做到这一点?

【问题讨论】:

你知道日期是独一无二的吗? 【参考方案1】:

这有点棘手。您可以在日期上full join,但随后您需要填补空白。 Postgres 不支持窗口函数上的ignore nulls,因此一种选择是使用条件和来构建组,然后first_value()

select
    date,
    first_value(income)  over(partition by grp_i order by date) income,
    first_value(expense) over(partition by grp_e order by date) expense
from (
    select
        date, 
        i.income,
        e.expense,
        count(*) filter(where i.income  is not null) over(order by date) grp_i,
        count(*) filter(where e.expense is not null) over(order by date) grp_e
    from incomes i
    full join expenses e using(date)
) t

另一方面,您也可以从选定的日期期间开始(使用generate_series()),然后将带有left joins 的表格带入。其余逻辑不变:

select
    date,
    first_value(income)  over(partition by grp_i order by date) income,
    first_value(expense) over(partition by grp_e order by date) expense
from (
    select
        d.date, 
        i.income,
        e.expense,
        count(*) filter(where i.income  is not null) over(order by d.date) grp_i,
        count(*) filter(where e.expense is not null) over(order by d.date) grp_e
    from generate_series(date '2020-02-12', date '2020-02-15', interval '1' day) d(date)
    left join incomes  i on i.date = d.date
    left join expenses e on e.date = d.date
) t
order by date

【讨论】:

【参考方案2】:

一种方法(如果您的数据不太大)是横向连接:

select gs.dte, i.income, e.expense
from generate_series('2020-02-12'::date, '2020-02-15'::date, interval '1 day'
                   ) gs(dte) left join lateral
     (select i.*
      from income i
      where i.date <= gs.dte
      order by i.date desc
      limit 1
     ) i
     on true left join lateral
     (select e.*
      from expense e
      where e.date <= gs.dte
      order by e.date desc
      limit 1
     ) e
     on true;

另一种选择是在每个表上独立使用generate_series() 来计算值。两者都从最早的日期开始,所以这是可行的:

select i.date, i.income, e.expense
from (select gs.date, i.income
      from (select i.*, lead(date) over (order by date) as next_date
            from income i
           ) i cross join lateral
           generate_series(date, coalesce(next_date - interval '1 day', '2020-02-15'::date), interval '1 day') gs(date)
     ) i join
     (select gs.date, e.expense
      from (select e.*, lead(date) over (order by date) as next_date
            from expense e
           ) e cross join lateral
           generate_series(date, coalesce(e.next_date - interval '1 day', '2020-02-15'::date), interval '1 day') gs(date)
     ) e 
     on i.date = e.date;

Here 是两个解决方案的 dbfiddle。

这很容易修改以处理两个系列不在同一日期开始的情况。

【讨论】:

以上是关于将两个表聚合成时间序列的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 10g 从两个不同的行和列聚合成一行

使用预先聚合的值加入两个表,既不会导致分组错误,也不会导致聚合错误

连接两个表的字符串的聚合函数?

s-s-rS 聚合聚合

用于合并来自同一个表的两个聚合子集的 SQL 查询

比较不同表的聚合函数