不能只靠GROUP BY,必须配合窗口函数或子查询计算前N日均值再比对当日值;单纯分组聚合无法定义“突增”,需先按天和账户聚合消费总额,再用窗口函数计算滚动均值并设定业务倍数阈值识别异常。

用GROUP BY + 突增阈值识别异常账户
直接结论:不能只靠 GROUP BY,必须配合窗口函数或子查询计算「前N日均值」,再比对当日值。单纯分组聚合只能统计单日数据,无法定义“突增”。
先算每日每个账户的消费总额
这是所有后续分析的基础。注意账户维度要明确——是 user_id 还是 account_no,字段名必须和业务一致;日期需归一到天级(别用带时分秒的 created_at 直接分组)。
SELECT DATE(created_at) AS day, user_id, SUM(amount) AS daily_amount FROM orders WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(created_at), user_id;
- 务必加时间范围限制(如最近7天),否则全表扫描太慢
-
DATE(created_at)在 MySQL 中安全;PostgreSQL 用created_at::date,SQLite 用date(created_at) - 如果存在退款订单,
amount可能为负,需确认是否应过滤或取ABS()
用窗口函数算“前3日均值”并对比当日值
突增的本质是偏离历史基线,推荐用 AVG() OVER 计算滚动均值,比自连接或子查询更简洁、可读性高,且避免关联爆炸。
WITH daily_user AS (
SELECT
DATE(created_at) AS day,
user_id,
SUM(amount) AS daily_amount
FROM orders
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY DATE(created_at), user_id
),
with_avg AS (
SELECT *,
AVG(daily_amount) OVER (
PARTITION BY user_id
ORDER BY day
ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING
) AS avg_last_3_days
FROM daily_user
)
SELECT
day, user_id, daily_amount, ROUND(avg_last_3_days, 2) AS avg_last_3_days
FROM with_avg
WHERE daily_amount > COALESCE(avg_last_3_days, 0) * 3;-
ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING表示取当前行前3天到前1天(不含当天),共3个值 -
COALESCE(avg_last_3_days, 0)防止新账户无历史数据导致NULL比较失败 - 突增倍数(这里是3倍)是业务参数,需根据实际数据分布调整,建议先查
PERCENTILE_CONT(0.95)看正常波动上限
警惕时间窗口错位和冷启动问题
最容易被忽略的是:刚注册的用户没有“前3日”数据,但他们的首日大额消费可能真属异常;而高频交易账户的“3倍”阈值可能过于宽松。这两类必须单独处理。
- 对注册不满3天的
user_id,改用全量历史均值或最小非零值替代avg_last_3_days - 若某日全站流量激增(如大促),全局突增会失真,应增加分位数过滤:只看
daily_amount > PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY daily_amount)的账户 - MySQL 8.0+、PostgreSQL 11+、SQL Server 2012+ 支持该窗口语法;旧版 MySQL 必须用自连接模拟,性能差且易出错
实际跑起来,重点不是写多复杂的 SQL,而是搞清“突增”在你业务里到底指什么——是绝对值突破阈值?还是相对自身历史偏离?前者用 HAVING 就够,后者绕不开窗口或子查询。

















