`GROUP BY`本身不会凭空丢数据,它只是把语义上“相同”的值合并了。但问题就出在“相同”的标准上——`NULL`值、前后空格、大小写、数字和字符串的混用,都可能让本该分开的行被强行归为一组。
一个典型的例子:你查`SELECT category, COUNT(*) FROM t GROUP BY category`,只返回了3行,但`SELECT DISTINCT category FROM t`却显示有12个不同的值。这明显不对劲。
**排查思路是这样的:**
1. 先看看原始分布情况:`SELECT category, COUNT(*) FROM t GROUP BY category ORDER BY COUNT(*) DESC LIMIT 5`,看看哪些类目数量异常。
2. 再对比一下清洗后的数据:`SELECT TRIM(UPPER(category)), COUNT(*) FROM t GROUP BY TRIM(UPPER(category))`。如果结果行数突然变多了,那基本可以断定是空格或大小写在捣乱。
3. 别忘了`NULL`值。`NULL`会被当作一个独立的分组,但很容易被忽略。你可以加个`WHERE category IS NOT NULL`来排除它,但前提是确认业务上确实不需要这部分数据。
4. 类型隐式转换也是个坑。比如`user_id`是整数类型,但你想按字符串语义分组(比如补零),那就得显式转换:`GROUP BY CAST(user_id AS CHAR)`。
JOIN后GROUP BY统计值失真,是不是笛卡尔积在作祟
这种情况,十有八九是JOIN产生了重复行。比如,你查订单信息,`orders JOIN order_items ON orders.id = order_items.order_id`,一个订单里有3个商品,那`orders`表里的数据就会跟着`order_items`膨胀成3行。然后你再`GROUP BY orders.id`,`COUNT(*)`自然就变成了3,而不是你期望的1。
**怎么验证和避免?**
* **先验证**:执行`SELECT orders.id, COUNT(*) FROM orders JOIN order_items ... GROUP BY orders.id ORDER BY COUNT(*) DESC LIMIT 3`,看看有没有明显大于1的计数。如果有很多订单的计数都大于1,那问题就坐实了。
* **避免方式**:最稳妥的办法是“先聚合,再JOIN”。用子查询或者CTE,先把`order_items`表按`order_id`汇总成`item_count`、`total_amount`这样的聚合结果,然后再和`orders`表关联。这样就不会有重复行的问题了。
* **别依赖`DISTINCT`救场**:`COUNT(DISTINCT orders.id)`能修数量,但对`SUM()`这类操作无效,而且它只是掩盖了JOIN逻辑上的问题,治标不治本。
HA VING写错位置或混用WHERE,结果就不可信
`HA VING`和`WHERE`的执行阶段完全不同,混用必然出错。`WHERE`是在分组前过滤行,而`HA VING`是在分组后过滤组。
一个很常见的错误:`SELECT department, COUNT(*) FROM employees WHERE COUNT(*) > 5 GROUP BY department`。`COUNT(*)`在`WHERE`阶段根本不存在,PostgreSQL会直接报语法错误,MySQL宽松模式下虽然能执行,但结果完全是随机的,毫无意义。
**正确的用法:**
* **`WHERE`放业务前置条件**:比如`WHERE status = 'active' AND created_at >= '2024-01-01'`,越早过滤,效率越高。
* **`HA VING`只用于分组后判断**:比如`HA VING COUNT(*) > 10`。注意,`HA VING status = 'active'`是非法操作,除非`status`字段也在`GROUP BY`中。
* **别名可用性不一致**:`HA VING total > 5000`在MySQL里可行(如果`SELECT SUM(amount) AS total`),但PostgreSQL必须写原始表达式`HA VING SUM(amount) > 5000`。
最后,再说一个容易被忽略的点:`GROUP BY`本身不保证结果的顺序。如果你需要特定的排序,必须显式地写`ORDER BY`。而且,`ORDER BY`中只能引用`SELECT`中的列或别名。
另外,如果你需要“每组内排序取前N”这种操作,用窗口函数(比如`ROW_NUMBER() OVER (PARTITION BY ...)`)会比硬套`GROUP BY`可靠得多,也灵活得多。