SQL GROUP BY 与聚合函数使用指南

11次阅读

group by 后 select 列必须为分组列或聚合函数,否则严格模式下报错;where 过滤原始行,having 过滤分组结果;null 在 group by 中被归为一组;聚合函数不可嵌套;跨数据库需注意模式差异。

SQL GROUP BY 与聚合函数使用指南

GROUP BY 后 SELECT 列必须是分组列或 聚合函数

这是最常报错的点:SELECT col1, col2 FROM t GROUP BY col1 在严格模式下直接报错,因为 col2 既没在 GROUP BY 里,也没套聚合函数。MySQL 5.7+ 默认启用 ONLY_FULL_GROUP_BY,PostgreSQL 和 SQL Server 一直严格遵循这条规则。

实操建议:

  • 检查当前 SQL 模式:执行 SELECT @@sql_mode,确认是否含 ONLY_FULL_GROUP_BY
  • 要么把所有非聚合列加进 GROUP BY(如 GROUP BY col1, col2),要么用聚合函数包裹(如 MAX(col2)ANY_VALUE(col2)
  • ANY_VALUE() 是 MySQL 特有函数,不推荐用于业务关键逻辑——它不保证返回哪一行的值,只是绕过校验

WHERE 和 HAVING 混用时的执行顺序搞反了

WHERE 过滤的是原始行,HAVING 过滤的是分组后的结果。写成 WHERE COUNT(*) > 1 肯定报错,因为 COUNT 是聚合函数,还没到 HAVING 阶段。

实操建议:

  • 想筛“订单数大于 5 的用户”,用 HAVING COUNT(*) > 5,不能放 WHERE
  • 想筛“2024 年的订单再按用户分组”,时间条件必须写在 WHERE(如 WHERE order_time >= '2024-01-01'),否则会先分组再过滤,性能差还可能漏数据
  • PostgreSQL 不允许 HAVING 引用未出现在 SELECTGROUP BY 中的列,MySQL 相对宽松但行为不可靠

NULL 值在 GROUP BY 中被当成同一组

GROUP BY col 时,所有 NULL 值会被归为一组——这不是 bug,是 SQL 标准行为。但很多人误以为 NULL 会被忽略或单独分组失败。

实操建议:

  • 如果业务上需要区分“空字符串”和“NULL”,提前用 COALESCE(col, 'NULL_VAL')CASE WHEN col IS NULL THEN 'unknown' ELSE col END 处理
  • 想排除 NULL 再分组?加 WHERE col IS NOT NULL,别指望 HAVING 能筛掉它们
  • 某些 ORM(如 Django ORM)生成的 GROUP BY 语句可能隐式包含 IS NULL 判断,注意看最终 SQL

聚合函数嵌套导致语法错误或逻辑错误

SUM(AVG(x))MAX(COUNT(*)) 这类写法在标准 SQL 中非法——聚合函数不能直接嵌套。有人想“取每组平均值的最大值”,得用子查询或 CTE。

实操建议:

  • 想算“各城市平均订单金额的最高值”,写两层:SELECT MAX(avg_amt) FROM (SELECT AVG(amount) AS avg_amt FROM orders GROUP BY city) t
  • 窗口函数能简化部分场景,比如 AVG(amount) OVER (PARTITION BY city) 可以避免先分组再聚合,但注意它不减少行数
  • SQLite 不支持窗口函数(3.25+ 才开始支持),MySQL 8.0+、PostgreSQL 9.4+ 支持较完整

GROUP BY 看似简单,但实际出问题往往不是语法写错,而是对“分组时机”“NULL 归类规则”“聚合作用域”的理解偏差。尤其跨数据库迁移时,MySQL 的宽松模式容易掩盖问题,一换 PostgreSQL 就崩。

text=ZqhQzanResources