获取重叠时间段的计数
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.
我在 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.