group by前漏写非聚合字段会报错,需确保select中所有非聚合字段均出现在group by中或用聚合函数包裹;渠道字段须清洗统一格式并排除空值;计算占比优先用窗口函数sum(count(*)) over();跨时区统计应转换时区或用时间范围过滤。

GROUP BY 前漏写非聚合字段,直接报错
MySQL 8.0+ 和 PostgreSQL 默认开启严格模式,SELECT 列表里只要出现没被 GROUP BY 包含、又没套 MAX()/COUNT() 等聚合函数的字段,就会报错:Expression #1 of SELECT list is not in GROUP BY clause。
比如想按渠道统计访问量,却写了:SELECT channel, page, COUNT(*) FROM traffic GROUP BY channel —— 这里 page 既没分组也没聚合,必然失败。
- 正确做法:所有非聚合字段必须出现在
GROUP BY后,或改用聚合函数包裹(如MAX(page)) - 如果只关心渠道维度,就别把
page放在SELECT里 - 临时绕过(不推荐):MySQL 可关
sql_mode中的ONLY_FULL_GROUP_BY,但会掩盖逻辑问题
渠道字段含空值或杂乱字符串,导致分组结果失真
真实日志里 channel 经常是 NULL、空字符串、大小写混用(wechat vs WeChat)、带空格(" ios "),这些都会被当成不同分组。
统计前不清洗,可能看到 “ios”、“IOS”、“ ios ” 各占 1%——其实全是 iOS 流量。
- 用
TRIM(UPPER(channel))统一格式,再配合CASE WHEN归并近似值(如把'wx'、'weixin'都映射为'wechat') -
WHERE channel IS NOT NULL AND TRIM(channel) != ''排掉脏数据,避免空分组干扰占比计算 - 先跑
SELECT DISTINCT channel FROM traffic LIMIT 20快速扫一眼实际取值,比看文档更可靠
需要同时算总量和各渠道占比,别硬套子查询
想输出每渠道人数 + 占比(如 “微信:1200(24%)”),有人会写两层嵌套:SELECT channel, cnt, cnt/(SELECT SUM(cnt) FROM (...))。性能差,还容易因 NULL 或除零崩。
更稳的写法是用窗口函数一次算完,兼容 MySQL 8.0+、PostgreSQL、SQL Server:
SELECT channel, COUNT(*) AS cnt, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) AS pct FROM traffic WHERE channel IS NOT NULL GROUP BY channel;
-
SUM(COUNT(*)) OVER()是关键:窗口函数在分组后对整张结果集求和,不用反复扫描原表 - 乘
100.0强制转浮点,避免整数除法截断(MySQL 中5/10得0) - 如果数据库不支持窗口函数(如旧版 MySQL),就用 JOIN 关联一次聚合结果,别用相关子查询
时间范围跨天但没考虑时区,凌晨流量被切错天
用户行为日志存的是 UTC 时间,而运营要看“今天微信来了多少人”,直接 WHERE DATE(created_at) = '2024-06-15' 会把北京时间 6 月 15 日 00:00–07:59 的流量全算成 6 月 14 日(UTC+0)。
- 统一转成本地时区再截日期:
DATE(CONVERT_TZ(created_at, '+00:00', '+08:00'))(MySQL) - 或者更安全:用时间戳范围代替
DATE()函数,避免索引失效:created_at >= '2024-06-15 00:00:00' AND created_at ,再用 <code>CONVERT_TZ包裹左右边界 - 确认数据库时区设置:
SELECT @@time_zone,别假设它和你本地一致
实际跑的时候,GROUP BY 本身不难,难的是分组前的数据状态——字段有没有空、格式齐不齐、时间对不对时区,这些地方一松动,结果就偏得没影。










