在数据库查询中,提取某一列的最大与最小值是极其常见的需求,例如统计商品价格区间、查找最早与最晚的登录时间等。SQL通过聚合函数MAX和MIN提供了原生支持,但在实际业务中往往还需要连带取出极值所在行的其他字段,这就涉及子查询、窗口函数等不同写法。

一、MAX与MIN的基础用法
MAX函数返回指定列的最大值,MIN函数返回最小值,它们都属于SQL标准中的聚合函数。当作用于数值、日期甚至字符串类型时,数据库会按对应类型的排序规则得出结果。需要特别注意的是,MAX和MIN都会自动忽略NULL值,如果列中所有非空值都不存在,则返回NULL。
以下示例从orders表中取出订单金额的最高值与最低值:
-- 基础聚合查询,返回整表的最大最小金额 SELECT MAX(amount) AS max_amount, MIN(amount) AS min_amount FROM orders;
这种写法最为简单,执行计划通常只需一次全表扫描或索引扫描。但若在amount列上建有B树索引,数据库优化器可能直接读取索引两端叶子节点,效率极高。不过它只能返回极值本身,无法顺带拿到下单用户、时间等关联信息。
二、按分组提取最大最小值
业务常需按类别分别看极值,例如每个商品分类的最高与最低价。此时在SELECT中使用MAX、MIN,并结合GROUP BY分组列即可。分组聚合会在每个分组内独立计算,不会跨组比较。
示例按category_id分组统计:
-- 每个分类的价格极值 SELECT category_id, MAX(price) AS highest_price, MIN(price) AS lowest_price FROM products GROUP BY category_id ORDER BY category_id;
使用分组聚合时,SELECT列表只能出现GROUP BY列与聚合函数,若写入非分组列会引发报错或返回不确定值,这取决于数据库的配置模式。在MySQL的ONLY_FULL_GROUP_BY关闭时虽不报错,但结果并不可靠,因此应严格遵循聚合规范。
对于分组后还需筛选极值范围的场景,可用HAVING子句,例如只保留最高价大于100的分组:
SELECT category_id, MAX(price) AS highest_price FROM products GROUP BY category_id HAVING MAX(price) > 100;
三、取出极值所在行的其他字段
基础聚合只能给数字,实际常需知道“价格最高的那件商品叫什么”。这时有几种典型方案。第一种是用子查询把极值算出来,再作为条件关联原表。
-- 找出价格等于最低价的商品明细 SELECT * FROM products WHERE price = (SELECT MIN(price) FROM products);
该写法逻辑清晰,但子查询会执行一次,若原表很大且无合适索引,可能扫描两遍。另一种写法利用窗口函数,在不聚合掉行的前提下标记极值:
-- 用窗口函数标记每行是否为价格最大或最小 SELECT product_name, price, CASE WHEN price = MAX(price) OVER() THEN '最高' ELSE '' END AS is_max, CASE WHEN price = MIN(price) OVER() THEN '最低' ELSE '' END AS is_min FROM products;
窗口函数MAX(price) OVER()在不分组时对整个集求极值,并为每行附加比较结果。这种方式只需一遍扫描,且保留全部明细,适合报表展示。但要注意OVER()内若加PARTITION BY则变成分组内极值,使用时应明确业务含义。
四、字符串与日期类型的极值
MAX和MIN不只用于数字。对字符串按字符集排序取最大最小,对日期取最新最旧同样有效。例如查找最近注册用户:
-- 最早与最晚注册时间 SELECT MIN(register_time) AS first_user, MAX(register_time) AS last_user FROM user_account;
在字符串场景中,若字段混杂大小写,不同数据库排序规则会影响结果,例如PostgreSQL默认区分大小写,MySQL依赖collation设置。因此在文本极值判断前,建议统一使用LOWER或指定排序规则,避免环境差异导致数据偏差。
五、性能与索引建议
当表数据量达到千万级,频繁取极值应依赖索引。对数值或时间列建B树索引后,MAX一般可转化为“索引逆序首行”,MIN转化为“索引正序首行”,复杂度接近O(1)。可用EXPLAIN观察是否出现Index Scan或Top-N操作。
若业务同时要求最大与最小,分开两个索引扫描通常比全表聚合更快;部分数据库支持在一条语句里用两个聚合,优化器也能分别走索引两端。相比之下,子查询关联原表若没命中覆盖索引,则容易产生额外开销,此时窗口函数方案在只读分析库上往往更平稳。
| 方案 | 能否带明细 | 扫描次数 | 适用场景 |
|---|---|---|---|
| 基础聚合 | 否 | 1次 | 仅要极值数字 |
| 子查询关联 | 能 | 2次左右 | 取极值行且逻辑简单 |
| 窗口函数 | 能 | 1次 | 报表保留全量并标注 |
综合运用上述语句与索引策略,便能用SQL高效且准确地取出所需的最大与最小值,并灵活扩展到分组及明细查询中。