如果我分析 2020 年的第 22 周,活跃客户将是自 2019 年 22 周以来至少购买过一次的客户。
你身边的问题
这种方法有一些极端情况的歧义/问题:
如果您从“去年的同一日期”开始时间范围,则由于闰年,误差范围仅为 1 / 365 或 ~ 0.3 %。
固定的“365 天”(或 366 天)将完全消除偏差。
SQL 端的问题
很遗憾,window functions 目前不允许使用 DISTINCT 关键字(有充分的理由)。所以某种形式:
SELECT count(DISTINCT email) OVER (ORDER BY year, week
GROUPS BETWEEN 52 PRECEDING AND 1 PRECEDING)
FROM ...
.. 触发器:
ERROR: DISTINCT is not implemented for window functions
GROUPS keyword 仅在 Postgres 10 中添加,否则正是我们所需要的。
更重要的是,您的奇数帧定义甚至不会完全起作用,因为要考虑的周数并不总是 52,如上所述。
所以我们必须自己动手。
解决方案
以下仅生成所有感兴趣的周数,并计算每个周的不同客户数。很简单,只是日期数学从来都不是完全简单的。但是,根据您的设置细节,可能会有更快的解决方案。 (我还有其他几个想法。)
报告的时间范围可能会发生变化。这是一个辅助函数,用于生成给定年份的周数:
CREATE OR REPLACE FUNCTION f_weeks_of_year(_year int)
RETURNS TABLE(year int, week int, week_start timestamp)
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
ROWS 52 COST 10 AS
$func$
SELECT _year, d.week::int, d.week_start
FROM generate_series(date_trunc('week', make_date(_year, 01, 04)::timestamp) -- first day of first week
, LEAST(date_trunc('week', localtimestamp), make_date(_year, 12, 28)::timestamp) -- latest possible start of week
, interval '1 week') WITH ORDINALITY d(week_start, week)
$func$;
呼叫:
SELECT * FROM f_weeks_of_year(2020);
它每周返回 1 行,但在当前年份的当前周停止。 (未来年份的空集。)
计算基于these facts:
- 一年中的第一个 ISO 周始终包含 1 月 4 日。
- 最后一周的 ISO 不能在 12 月 28 日之后开始。
使用WITH ORDINALITY 即时计算实际周数。见:
除此之外,我坚持使用timestamp 并避免使用timestamptz。见:
该函数还返回一周开始的时间戳 (week_start),对于手头的问题,我们不需要它。但我把它留了下来,以使该功能在一般情况下更有用。
使主查询更简单:
WITH weekly_customer AS (
SELECT DISTINCT
EXTRACT(YEAR FROM orderdate)::int AS year
, EXTRACT(WEEK FROM orderdate)::int AS week
, email
FROM orders_table
WHERE paid
AND orderdate >= date_trunc('week', timestamp '2019-01-04') -- max range for 2020!
ORDER BY 1, 2, 3 -- optional, might improve performance
)
SELECT d.year, d.week
, (SELECT count(DISTINCT email)
FROM weekly_customer w
WHERE (w.year, w.week) >= (d.year - 1, d.week) -- row values, see below
AND (w.year, w.week) < (d.year , d.week) -- exclude current week
) AS active_customers
FROM f_weeks_of_year(2020) d; -- (year int, week int, week_start timestamp)
db小提琴here
CTE weekly_customer 每个日历周对唯一客户折叠一次,因为重复条目只是我们计算的噪音。它在主查询中多次使用。截止条件再次基于 1 月 4 日。根据您的实际报告期进行调整。
实际计数是通过低相关性子查询完成的。可能是LEFT JOIN LATERAL ... ON true。见:
使用行值比较来简化范围定义。见: