获取重叠时间段的计数

Getting counts for overlapping time periods

我在 PostgreSQL 中有一个 table data,结构如下:

created_at.       customer_email               status
2020-12-31        xxx@gmail.com                opened
...
2020-12-24        yyy@gmail.com                delivered
2020-12-24        xxx@gmail.com                opened
...
2020-12-17        zzz@gmail.com                opened
2020-12-10        xxx@gmail.com                opened
2020-12-03        hhh@gmail.com                enqueued
2020-11-27        xxx@gmail.com                opened
...
2020-11-20        rrr@gmail.com                opened
2020-11-13        ttt@gmail.com                opened

每天有很多行。

基本上我这周需要 2021-W01,其中包含过去 90 天内状态为“已打开”的唯一电子邮件的数量。之前的每个星期也是如此。

期望输出:

period    active
2021-W01  1539
2020-W53  1480
2020-W52  1630
2020-W51  1820
2020-W50  1910
2020-W49  1890
2020-W48  2000

我该怎么做?

您可以像这样将 date_part() 函数与分组方式结合使用:

SELECT
  DATE_PART('year', created_at)::varchar || '-W' || DATE_PART('week', created_at)::varchar,
  SUM(CASE WHEN status = 'opened' THEN 1 ELSE 0 END)
FROM
  your_table
GROUP BY 1
ORDER BY created_at DESC

Window functions 会浮现在脑海中。 las,那些不允许 DISTINCT 聚合。

相反,从 LATERAL 子查询中获取非重复计数:

WITH weekly_dist AS (
   SELECT DISTINCT date_trunc('week', created_at) AS wk, customer_email
   FROM   tbl
   WHERE  status = 'opened'
   )
SELECT to_char(t.wk, 'YYYY"-W"IW') AS period, ct.active
FROM  (
   SELECT generate_series(date_trunc('week', min(created_at) + interval '1 week')
                        , date_trunc('week', now()::timestamp)
                        , interval '1 week') AS wk   
   FROM   tbl
   ) t
LEFT   JOIN LATERAL (
   SELECT count(DISTINCT customer_email) AS active
   FROM   weekly_dist d
   WHERE  d.wk >= t.wk - interval '91 days'
   AND    d.wk <  t.wk
   ) ct ON true;

db<>fiddle here

我用 timestamp 操作,而不是 timestamptz,可能会导致极端情况有所不同。

CTE weekly_dist 将集合减少为不同的“已打开”电子邮件。此步骤完全是可选的,但如果每周可以重复多次,则会显着提高性能。

派生的 table t 为从 table 中最早的条目到“现在”的每个星期的开始生成一个时间戳。这样我就可以确保没有一周被跳过,即使没有行。参见:

  • PostgreSQL: running count of rows for a query 'by minute'
  • Generating time series between two dates in PostgreSQL

但我确实跳过了第一周,因为我在每个星期的开始之前计算活跃的电子邮件。

然后 LEFT JOIN LATERAL 到计算 90 天时间范围内非重复计数的子查询。准确地说,我扣除了 91 天,并排除了本周的开始。这恰好与 CTE 的每周预汇总数据一致。如果您移动边界,请注意这一点。

最后,to_char(t.wk, 'YYYY"-W"IW') 是一个紧凑的表达式,用于获取所需的周数格式。手册中的详细信息 here.