group by 是 min/max 按分类取边界值的硬性前提,漏写会导致报错或结果错位;非聚合字段必须全部出现在 group by 中;min/max 默认忽略 null,但全 null 组返回 null;字符串按字典序比较,非长度或时间;where 必须在 group by 前过滤行,having 用于过滤分组。

MIN/MAX 在 GROUP BY 中必须配合分组才能返回有意义的边界值
单独写 SELECT MIN(price), MAX(price) 只会返回整张表的全局最小和最大值,不是每个分类的。要按分类取边界,GROUP BY 是硬性前提,漏掉就会结果错位或报错(尤其在严格模式下)。
常见错误是写成:SELECT category, MIN(price) FROM products —— 没有 GROUP BY category,MySQL 5.7+ 会直接报错 Expression #1 of SELECT list is not in GROUP BY clause,PostgreSQL 和 SQL Server 更早就不允许这种写法。
- 必须显式写出
GROUP BY category(或对应分类字段名) - SELECT 列中所有非聚合字段,都得出现在 GROUP BY 中
- 如果分类字段是
product_type,那就用GROUP BY product_type,别凭印象写成category
处理 NULL 值时 MIN/MAX 默认忽略,但需确认业务是否真要忽略
MIN() 和 MAX() 天然跳过 NULL,这通常符合预期,但容易忽略一个关键点:如果某分类下所有值全是 NULL,结果会返回 NULL,而不是“无数据”。这对报表或下游逻辑可能造成隐性问题。
比如统计各地区订单金额极值,若某地区还没产生订单,amount 全为 NULL,MIN(amount) 和 MAX(amount) 都返回 NULL,而非 0 或空字符串。是否需要补默认值,得看业务定义。
- 用
COALESCE(MIN(price), 0)把 NULL 转成 0(仅适用于数值场景) - 字符串类型慎用
COALESCE(MAX(name), 'N/A'),避免把真实空字符串也覆盖 - 更稳妥的做法是先用
COUNT(price)判断该组是否有有效值
字符串类型的 MAX/MIN 按字典序比较,不是按长度或语义
对字符串字段(如 product_name)用 MAX(),得到的是字典序最大的值,不是最长的、也不是最新录入的。例如:['Apple', 'Banana', 'Cherry'] 的 MAX() 是 'Cherry',而 ['Zebra', 'apple'] 的 MAX() 是 'Zebra'(因为大写字母 ASCII 值更小,但多数数据库默认不区分大小写排序,实际行为依赖 collation 设置)。
如果你真正想要“最新名称”或“最长名称”,MAX() 无法满足,得换思路:
- 找最长字符串:用
ORDER BY LENGTH(name) DESC LIMIT 1配合窗口函数或子查询 - 找最新记录的名称:得依赖时间戳字段,比如
MAX(created_at)找时间,再关联取对应name - 确认当前数据库的 collation(如
utf8mb4_0900_as_cs),避免大小写影响字典序结果
性能注意:WHERE 条件必须放在 GROUP BY 前,否则过滤失效
想查“2024 年各分类价格极值”,容易写成:SELECT category, MIN(price), MAX(price) FROM products GROUP BY category WHERE created_at >= '2024-01-01' —— 这语法错误,WHERE 必须在 GROUP BY 前。
更隐蔽的问题是把条件放错位置导致逻辑错误。比如想排除测试数据,却写成:GROUP BY category HAVING category != 'TEST' —— 这是在分组后过滤分组,而不是过滤原始行。若某分类只有测试数据,它仍会被分组、再被 HAVING 剔除;但若混有正常数据,测试数据仍参与了 MIN/MAX 计算。
- 行级过滤用
WHERE:写在GROUP BY之前,确保只对符合条件的行聚合 - 分组级过滤用
HAVING:只能跟在GROUP BY后,且只能引用聚合结果或分组字段 - 示例正确写法:
SELECT category, MIN(price), MAX(price) FROM products WHERE status = 'active' GROUP BY category
WHERE price BETWEEN 0 AND 10000 卡范围。











