“每天活跃且累计金额达标”指对每个日期d,筛选出在d有行为、且从首笔交易至d(含)历史累计金额≥阈值的用户,本质是截止每日的滚动累计达标判断,需用窗口函数按用户时间序计算累积和后筛选。

什么是“每天活跃且累计金额达标”的真实语义
很多人看到这个需求第一反应是写个 GROUP BY DATE(created_at) 再加 HAVING SUM(amount) >= X,但这是错的——它统计的是“当天行为累计达标”的用户,不是“到当天为止历史累计达标”的用户。真正要的是:对每个日期 d,找出所有在 d 有行为、且从首笔交易起至 d(含)的总金额 ≥ 阈值的用户。
这本质是“截止到每日的滚动累计达标判断”,必须引入窗口函数或自连接模拟累积逻辑。
用窗口函数实现每日达标用户统计(推荐)
核心思路:先按用户+时间排序算出每人每日的累计金额,再筛选出“当日有行为”且“截至当日累计 ≥ 阈值”的记录。
SELECT DATE(event_time) AS dt, COUNT(DISTINCT user_id) AS active_cumulative_users
FROM (
SELECT
user_id,
event_time,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY event_time
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cum_amount
FROM orders
WHERE event_time >= '2024-01-01'
) t
WHERE cum_amount >= 500 -- 达标阈值
GROUP BY DATE(event_time);
注意点:
-
ORDER BY event_time必须严格有序,如果存在秒级相同时间,建议加上user_id或主键做二级排序防非确定性 -
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是默认行为,但显式写出更安全 - 如果表里有退款/负向金额,
SUM(amount)会自动抵扣,符合业务逻辑;若需只计正向流水,得提前过滤WHERE amount > 0
MySQL 5.7 或不支持窗口函数时怎么处理
只能靠自连接或变量模拟累计和,性能差但可行。典型做法是:
SELECT DATE(t1.event_time) AS dt, COUNT(DISTINCT t1.user_id) AS cnt
FROM orders t1
JOIN orders t2
ON t1.user_id = t2.user_id
AND t2.event_time <= t1.event_time
AND t2.event_time >= DATE_SUB(t1.event_time, INTERVAL 365 DAY) -- 防笛卡尔爆炸,加时间范围限制
GROUP BY t1.user_id, DATE(t1.event_time)
HAVING SUM(t2.amount) >= 500;
关键约束:
- 不加
t2.event_time时间范围限制会导致全量自连接,数据量稍大就卡死 -
GROUP BY t1.user_id, DATE(t1.event_time)是为了确保“每人每天”只算一次达标状态 - 这种写法在百万级用户+年数据下基本不可用,仅作兜底方案
容易被忽略的边界情况
- 同一用户同一天多笔订单:窗口函数天然支持,无需去重;但自连接方式若没控制好 JOIN 条件,可能重复累加
- 用户首单即达标:没问题,窗口函数从第一行就开始累计
- 达标后又退款导致累计跌破阈值:按业务定义,只要“截至当天”累计 ≥ 阈值就算当日达标,哪怕次日退回也不影响当日结果
- 时间字段是字符串或带时区:务必先用 STR_TO_DATE() 或 CONVERT_TZ() 标准化,否则 DATE() 可能截断错误
窗口函数是这个问题的合理解法,但它的正确性高度依赖排序字段的唯一性和时序完整性。一旦 event_time 有空值、乱序或精度不足,累计和就会错位,这种问题在线上往往静默发生,很难排查。

















