生命周期阶段必须在CTE或子查询中预先为每个用户计算并固化,不可在GROUP BY中用CASE WHEN动态判断,因阶段由用户首单时间决定且需全订单一致;渠道归属须与生命周期同源,以首单渠道为准。

生命周期阶段必须在子查询或CTE中预先算好
直接在GROUP BY里用CASE WHEN动态判断“当前订单属于用户哪个生命周期阶段”,结果一定错。因为阶段归属由用户首单时间决定,不是每条订单独立可算的——同一个用户的不同订单,阶段值必须一致。若在最外层GROUP BY channel, CASE WHEN order_date = MIN(order_date) OVER (PARTITION BY user_id) THEN 'acquisition' ...,SQL引擎会报错(列未在GROUP BY中)或逻辑混乱(窗口函数不能嵌套在GROUP BY表达式中)。
正确做法是先用CTE固化每个用户的阶段:
WITH user_lifecycle AS (
SELECT
user_id,
MIN(order_date) AS first_order_date,
CASE
WHEN DATEDIFF('day', MIN(order_date) OVER (PARTITION BY user_id), order_date) = 0 THEN 'acquisition'
WHEN DATEDIFF('day', MIN(order_date) OVER (PARTITION BY user_id), order_date) BETWEEN 1 AND 30 THEN 'growth'
ELSE 'mature'
END AS lifecycle_stage
FROM orders
GROUP BY user_id, order_date -- 注意:这里GROUP BY要包含order_date才能保留明细行上下文
)但更稳妥的是把阶段打在订单主表上,用LEFT JOIN关联预计算的用户维度表(含user_id、first_order_date、first_channel),避免在聚合前重复计算。
分组时别漏掉行为时间窗口过滤
活跃度不是“历史上所有订单”,而是“在指定时间段内有行为的用户数”。如果只GROUP BY channel, lifecycle_stage却没限制order_date范围,就会把2022年的老用户计入2026年8月的活跃统计,结果严重虚高。
- WHERE条件必须放在JOIN之后、GROUP BY之前,且作用于订单时间字段,例如
WHERE o.order_date >= '2026-08-01' - 不能用
HAVING代替——它过滤的是分组后结果,无法剔除不该计入的订单行 - 若需按月趋势分析,
GROUP BY应包含DATE_TRUNC('month', o.order_date)(PostgreSQL)或YEAR(o.order_date), MONTH(o.order_date)(MySQL),但lifecycle_stage仍来自用户维度固定值,不可随月份变动
渠道归属逻辑必须与生命周期对齐
用户A首次访问来自微信,但首单在App完成——该算哪个渠道?答案是:以首单渠道为准。生命周期阶段和渠道归属必须同源,否则交叉分析会失真。
常见错误是分别查“首次曝光渠道”和“首单时间”,再强行拼接。正确路径是:
- 提前构建
user_first_channel宽表,字段为user_id、first_channel、first_order_date - 订单表
orders通过user_id左连该表,确保每笔订单带所属用户的首单渠道和阶段标签 - 聚合时用
COUNT(DISTINCT user_id),而非COUNT(*)——同一用户当月多单只计1次活跃 - 显式处理
NULL渠道:CASE WHEN first_channel IS NULL THEN 'direct' ELSE first_channel END,否则GROUP BY会丢弃整行
PostgreSQL与MySQL在日期函数上的关键差异
用MIN(order_date) OVER (PARTITION BY user_id)算首单日没问题,但后续阶段判断的日期差函数不通用:
- PostgreSQL推荐用
order_date - first_order_date(返回interval),再转天数:(order_date - first_order_date)::int - MySQL必须用
TIMESTAMPDIFF(DAY, first_order_date, order_date);DATEDIFF()会截断时分秒,导致跨日订单被算成0天 - BigQuery用
DATE_DIFF(order_date, first_order_date, DAY),且要求输入为DATE类型,若字段含时间需先DATE(order_date) - 所有数据库都要求
first_order_date和order_date时区一致——若原始数据混用UTC和本地时区,必须统一转换,否则差值偏差可达数小时甚至一天
阶段边界定义容易被忽略:比如“成长期”定义为“首单后1–30天”,但第30天23:59下单的用户,若时区未对齐,可能被误判为“成熟期”。实际生产中,建议统一转为UTC后再计算天数差,并用闭区间BETWEEN 1 AND 30明确包含端点。

















