【问题标题】:GROUP BY next months over N yearsGROUP BY 未来几个月超过 N 年
【发布时间】:2015-10-21 12:54:38
【问题描述】:

我需要汇总 5 年内 12 个下个月按“水平”分组的金额:
假设我们是 2015-08-15

SUM amount from  0 to 12 next months (from 2015-08-16 to 2016-08-15)
SUM amount from 12 to 24 next months (from 2016-08-16 to 2017-08-15)
SUM amount from 24 to 36 next months ...
SUM amount from 36 to 48 next months
SUM amount from 48 to 60 next months

这是一个fiddled 数据集示例:

+----+------------+--------+
| id | date       | amount |
+----+------------+--------+
|  1 | 2015-09-01 |     10 |
|  2 | 2015-10-01 |     10 |
|  3 | 2016-10-01 |     10 |
|  4 | 2017-06-01 |     10 |
|  5 | 2018-06-01 |     10 |
|  6 | 2019-05-01 |     10 |
|  7 | 2019-04-01 |     10 |
|  8 | 2020-04-01 |     10 |
+----+------------+--------+

这是预期的结果:

+---------+--------+
| horizon | amount |
+---------+--------+
|       1 |     20 |
|       2 |     20 |
|       3 |     10 |
|       4 |     20 |
|       5 |     10 |
+---------+--------+

我怎样才能得到这 12 个月的分组“视野”?


我标记了 PostgreSQL,但我实际上使用的是 ORM,所以只是为了找到这个想法。 (顺便说一下,我无法使用日期格式化功能)

【问题讨论】:

  • 减去 8.5 个月并按年分组。

标签: sql postgresql group-by aggregation


【解决方案1】:

我会按 12 个月的时间范围划分并按此分组:

SELECT
  FLOOR(
      (EXTRACT(EPOCH FROM date) - EXTRACT(EPOCH FROM now()))
        / EXTRACT(EPOCH FROM INTERVAL '12 month')
    ) + 1 AS "horizon",
  SUM(amount) AS "amount"
FROM dataset 
GROUP BY horizon
ORDER BY horizon;

SQL Fiddle

灵感来自:Postgresql SQL GROUP BY time interval with arbitrary accuracy (down to milli seconds)

【讨论】:

  • 对我来说最简单最聪明的答案
  • 感谢和 +1 提供灵感链接。
  • 注意 (a / b) - (c / b) = (a - c) / b。后者的计算速度几乎快两倍。另请注意,epoch from interval '12 month' 给您的秒数等于 365.25 天。根据您所在的年份以及您要比较的年份(闰年与否),结果可能是任一方向的休息日。
  • 感谢分解,它也使它更简单,我已经根据这个更新了答案和小提琴
【解决方案2】:

很简单:

SELECT horizon, sum(amount) AS amount
FROM generate_series(1, 5) AS s(horizon)
JOIN dataset ON "date" >= current_date + (horizon - 1) * interval '1 year'
             AND "date" < current_date + horizon * interval '1 year'
GROUP BY horizon
ORDER BY horizon;

【讨论】:

    【解决方案3】:

    也许是 CTE?

    WITH RECURSIVE grps AS
    (
      SELECT 1 AS Horizon, (date '2015-08-15') + interval '1' day AS FromDate, (date '2015-08-15') + interval '1' year AS ToDate
      UNION ALL
      SELECT Horizon + 1, ToDate + interval '1' day AS FromDate, ToDate + interval '1' year
      FROM grps WHERE Horizon < 5
    )
    SELECT 
      Horizon, 
      (SELECT SUM(amount) FROM dataset WHERE date BETWEEN g.FromDate AND g.ToDate) AS SumOfAmount
    FROM 
      grps g
    

    SQL fiddle

    【讨论】:

    • 一个了不起的解决方案!太糟糕了,虽然有一个相当简单的解决方案,但它却如此复杂。
    【解决方案4】:

    假设您需要从当前日期到明年这一天的间隔等等,我会这样查询:

    SELECT 1 AS horizon, SUM(amount) FROM dataset
    WHERE date > now()
    AND date < (now() + '12 months'::INTERVAL)
    UNION
    SELECT 2 AS horizon, SUM(amount) FROM dataset
    WHERE date > (now() + '12 months'::INTERVAL)
    AND date < (now() + '24 months'::INTERVAL) 
    UNION
    SELECT 3 AS horizon, SUM(amount) FROM dataset
    WHERE date > (now() + '24 months'::INTERVAL)
    AND date < (now() + '36 months'::INTERVAL)
    UNION
    SELECT 4 AS horizon, SUM(amount) FROM dataset
    WHERE date > (now() + '36 months'::INTERVAL)
    AND date < (now() + '48 months'::INTERVAL)
    UNION
    SELECT 5 AS horizon, SUM(amount) FROM dataset
    WHERE date > (now() + '48 months'::INTERVAL)
    AND date < (now() + '60 months'::INTERVAL)
    ORDER BY horizon;
    

    您可以对其进行概括并使用附加变量进行类似的操作:

    SELECT number AS horizon, SUM(amount) FROM dataset
    WHERE date > (now() + ((number - 1) * '12 months'::INTERVAL))
    AND date < (now() + (number * '12 months'::INTERVAL));
    

    其中number[1,5] 范围内的整数

    这是我从小提琴中得到的:

    | horizon | sum |
    |---------|-----|
    |       1 |  20 |
    |       2 |  20 |
    |       3 |  10 |
    |       4 |  20 |
    |       5 |  10 |
    

    【讨论】:

    • 您的number 将来自JOIN generate_series(1, 5) AS s(number)
    【解决方案5】:

    试试这个

    select 
    id,
    sum(case when date>=current_date and date<current_date+interval 1 year then amount else 0 end) as year1,
    sum(case when date>=current_date+interval 1 year and date<current_date+interval 2 year then amount else 0 end) as year2,
    sum(case when date>=current_date+interval 2 year and date<current_date+interval 3 year then amount else 0 end) as year3,
    sum(case when date>=current_date+interval 3 year and date<current_date+interval 4 year then amount else 0 end) as year4,
    sum(case when date>=current_date+interval 4 year and date<current_date+interval 5 year then amount else 0 end) as year5
    from table
    group by id
    

    【讨论】:

      【解决方案6】:

      你需要一个联合和一个聚合函数:

      select 1 as horizon, 
             sum(amount) amount
      from the_table
      where date >= current_date 
        and date < current_date + interval '12' month
      union all
      select 2 as horizon, 
             sum(amount) amount
      where date >= current_date + interval '12' month
        and date < current_date + interval '24' month
      union all 
      select 3 as horizon, 
             sum(amount) amount
      where date >= current_date + interval '24' month
        and date < current_date + interval '36' month
      ... and so on ...
      

      但我不知道如何使用混淆层(又名 ORM)来做到这一点,但我确信它支持(或者它应该)聚合和联合。

      这可以很容易地封装到一个 PL/PgSQL 函数中,您可以在其中传递“地平线”,并且 SQL 是动态构建的,因此您需要调用的只是:select * from sum_horizon(5) 其中5 表示年。


      顺便说一句:date 是一个可怕的列名称。一方面是因为它是一个保留字,但更重要的是因为它没有记录该列的含义。这是“发布日期”吗? “截止日期”? “订单日期”?

      【讨论】:

      • JOIN generate_series(1, 5) AS s(h) 然后current_date + s.h * interval '1 year' 等等?
      猜你喜欢
      • 1970-01-01
      • 2010-10-05
      • 2020-05-30
      • 1970-01-01
      • 1970-01-01
      • 2016-02-21
      • 2020-08-25
      • 1970-01-01
      相关资源
      最近更新 更多