用GROUP BY配合MAX()和MIN()可提取每组极值,如SELECT category, MAX(price), MIN(price) FROM products GROUP BY category;非聚合字段必须全在GROUP BY中,否则报错;若需极值对应完整记录,则须用窗口函数或子查询。

用 GROUP BY 配合 MAX() 和 MIN() 提取分组极值
直接在 SELECT 中对字段使用 MAX() 和 MIN(),再配合 GROUP BY 即可。这是最常用、最可靠的方式,适用于所有主流 SQL 数据库(MySQL、PostgreSQL、SQL Server、SQLite 等)。
常见错误是试图在没 GROUP BY 的情况下混用聚合函数和普通字段,比如:SELECT category, MAX(price), name FROM products —— 这会报错或返回不可靠的 name 值(MySQL 5.7+ 默认拒绝,旧版可能随机返回)。
- 必须把所有非聚合字段都写进
GROUP BY,例如按category分组就写GROUP BY category - 如果想同时获取最大值对应的整行记录(比如最贵商品的完整信息),
MAX()本身做不到,得用窗口函数或子查询 -
NULL值会被自动忽略——除非整组全是NULL,此时MAX()/MIN()返回NULL
需要带原始行数据时,改用窗口函数 ROW_NUMBER()
当你要的不只是“最大价格”,而是“每个分类里价格最高的那条商品记录(含 id、name、price 等全部字段)”,就得跳出聚合思维,用窗口函数。
核心思路:先用 ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) 给每组内记录按价格倒序编号,再筛选出编号为 1 的行。
- PostgreSQL / SQL Server / MySQL 8.0+ / Oracle 都支持该语法;MySQL 5.7 及更早版本不支持,需改用自连接或相关子查询
- 注意
ORDER BY price DESC中的DESC决定取“最大”还是“最小”——要最小值就改成ASC - 如果存在价格相同的情况,
ROW_NUMBER()会强制分配不同序号;想要保留所有并列最大值,换成RANK()或DENSE_RANK()
GROUP BY + 子查询匹配原始记录(兼容老版本 MySQL)
在不支持窗口函数的老系统(如 MySQL 5.6)中,典型做法是先用子查询求出各组极值,再用 JOIN 或 IN 关联回原表。
例如查每个 category 中价格最高的商品:
SELECT t1.* FROM products t1 INNER JOIN ( SELECT category, MAX(price) AS max_price FROM products GROUP BY category ) t2 ON t1.category = t2.category AND t1.price = t2.max_price
- 这个写法能拿到完整行,但有隐患:如果同一分类中有多个商品价格同为最高,会全部返回——这通常是期望行为;但如果只要一条,得额外加
LIMIT 1或引入主键排序 - 性能上,子查询结果集越小越好;确保
(category, price)有联合索引可显著提速 - 注意
AND t1.price = t2.max_price不能写成=比较浮点数,否则可能因精度问题漏匹配
别忽略 NULL 和空分组的影响
MAX() 和 MIN() 对 NULL 的处理很安静,容易被忽视——它们跳过 NULL,但不会报错或提醒你数据有缺失。
- 若某组所有值都是
NULL,MAX()返回NULL,不是 0 或空字符串 - 如果业务上“无数据”和“数据为 NULL”意义不同,建议提前用
WHERE price IS NOT NULL过滤,或用COALESCE(MAX(price), 0)做兜底 -
GROUP BY遇到全为NULL的字段时,不同数据库行为不一致:MySQL 把所有NULL归为一组,PostgreSQL 则视为独立组(需确认版本和配置)
真正麻烦的不是语法,而是搞清你要的是“每组的极值数字”,还是“极值所在的那条完整记录”——前者用聚合,后者得绕开聚合,选对路子才能少踩坑。

















