【问题标题】:Weekly total sums每周总金额
【发布时间】:2019-02-25 06:56:06
【问题描述】:

我在 PostgreSQL 数据库中有一个表,其中包含日期和每天的总计数。

我的日期总计 2012-05-12 12 2012-05-14 8 2012-05-13 4 2012-05-12 12 2012-05-15 2 2012-05-17 1 2012-05-18 1 2012-05-21 1 2012-05-25 1

现在我需要获取给定日期范围内的每周总计。
前任。我想获得从2012-05-012012-05-31 的每周总数。

我正在查看这个输出:

2012-05-01 2012-05-07 0 2012-05-08 2012-05-14 36 2012-05-15 2012-05-22 5 2012-05-23 2012-05-29 1 2012-05-30 2012-05-31 0

【问题讨论】:

    标签: sql postgresql aggregate-functions date-range


    【解决方案1】:

    这适用于任何给定的日期范围:

    CREATE FUNCTION f_tbl_weekly_sumtotals(_range_start date, _range_end date)
      RETURNS TABLE (week_start date, week_end date, sum_total bigint)
      LANGUAGE sql AS
    $func$
    SELECT w.week_start, w.week_end, COALESCE(sum(t.total), 0)
    FROM  (
       SELECT week_start::date, LEAST(week_start::date + 6, _range_end) AS week_end
       FROM   generate_series(_range_start::timestamp
                            , _range_end::timestamp
                            , interval '1 week') week_start
       ) w
    LEFT   JOIN tbl t ON t.mydate BETWEEN w.week_start and w.week_end
    GROUP  BY w.week_start, w.week_end
    ORDER  BY w.week_start
    $func$;
    

    呼叫:

    SELECT * FROM f_tbl_weekly_sumtotals('2012-05-01', '2012-05-31');
    

    要点

    • 为方便起见,我将其包装在一个函数中,因此日期范围只需提供一次。

    • 子查询w 生成从给定日期范围的第一天开始的周系列。上限以LEAST 为上限,以保持在给定日期范围的上限内。

    • 然后LEFT JOIN 到数据表(在我的示例中为tbl)以在结果中保留所有周,即使没有找到数据行。

    • 其余的应该是显而易见的。 COALESCE 输出 0 而不是 NULL 空周。

    • 数据类型必须匹配,我假设mydate datetotal int 缺少信息。 (intsum()bigint。)

    • 解释我对generate_series()的特殊用法:

    【讨论】:

      【解决方案2】:

      使用这个function

      CREATE OR REPLACE FUNCTION last_day(date)
      RETURNS date AS
      $$
        SELECT (date_trunc('MONTH', $1) + INTERVAL '1 MONTH - 1 day')::date;
      $$ LANGUAGE 'sql' IMMUTABLE STRICT;
      

      AND generate_series(从 8.4 开始)我们可以创建日期分区。

      SELECT wk.wk_start, 
             CAST(
                  CASE (extract(month from wk.wk_start) = extract(month from wk.wk_start + interval '6 days'))
                  WHEN true THEN wk.wk_start + interval '6 days'
                  ELSE last_day(wk.wk_start)
                  END 
                 AS date) AS wk_end
        FROM
          (SELECT CAST(generate_series('2012-05-01'::date,'2012-05-31'::date,interval '1 week') AS date) AS wk_start) AS wk;
      

      然后把它和数据放在一起

      CREATE TABLE my_tab(mydate date,total integer);
      INSERT INTO my_tab 
      values    
      ('2012-05-12'::date,12),
      ('2012-05-14'::date,8),
      ('2012-05-13'::date,4),
      ('2012-05-12'::date,12),
      ('2012-05-15'::date,2),
      ('2012-05-17'::date,1),
      ('2012-05-18'::date,1),
      ('2012-05-21'::date,1),
      ('2012-05-25'::date,1); 
      
      WITH month_by_week AS
          (SELECT wk.wk_start, 
             CAST(
                  CASE (extract(month from wk.wk_start) = extract(month from wk.wk_start + interval '6 days'))
                  WHEN true THEN wk.wk_start + interval '6 days'
                  ELSE last_day(wk.wk_start)
                  END 
                 AS date) AS wk_end
        FROM
          (SELECT CAST(generate_series('2012-05-01'::date,'2012-05-31'::date,interval '1 week') AS date) AS wk_start) AS wk
        )
      SELECT month_by_week.wk_start,
             month_by_week.wk_end,
             SUM(COALESCE(mt.total,0))
        FROM month_by_week 
             LEFT JOIN my_tab mt ON  mt.mydate BETWEEN month_by_week.wk_start AND month_by_week.wk_end
       GROUP BY month_by_week.wk_start,
                month_by_week.wk_end
       ORDER BY month_by_week.wk_start;
      

      【讨论】: