在日常数据查询中,我们经常会遇到这样的需求:一张表里存了语文、数学、英语三科成绩,需要找出每个学生三科中的最高分;或者一张库存表里有多个仓库的库存数量,需要取每个商品在所有仓库中的最大库存。这类问题的本质是在多个列之间比较取最大值,而很多人第一反应是使用MAX函数,结果发现查出来的结果不对。这篇文章就来详细讲解SQL中查询多列最大值的几种写法,以及聚合函数的组合使用技巧。

一、先弄清楚:MAX和GREATEST的本质区别
要正确写出多列最大值查询,第一步是分清两个容易混淆的函数:MAX和GREATEST。很多人查不出期望结果,根本原因就是把这两者搞混了。
MAX是一个聚合函数(Aggregate Function),它的作用对象是某一列的多行数据。也就是说,MAX会把一整列的值收集起来,然后返回其中最大的那一个,比如MAX(score)返回的是所有学生中某一科的最高分。聚合函数会把多行压缩成一行,所以经常会配合GROUP BY使用。
而GREATEST是一个标量函数(Scalar Function),也叫行级函数,它的作用对象是同一行内的多个列或多个值。GREATEST(a, b, c)会比较同一行里a、b、c三个列的值,返回其中最大的。它不会压缩行数,输入多少行就输出多少行。
简单总结一句话:MAX是纵向比较(列内跨行),GREATEST是横向比较(行内跨列)。查多列最大值,用GREATEST才是对路的。
二、GREATEST函数的基本用法与NULL陷阱
以学生成绩表为例,表结构如下:
-- 创建成绩表示例
CREATE TABLE student_score (
id INT PRIMARY KEY,
name VARCHAR(50),
chinese INT,
math INT,
english INT
);
INSERT INTO student_score VALUES (1, '张三', 85, 92, 78);
INSERT INTO student_score VALUES (2, '李四', 90, 66, 88);
INSERT INTO student_score VALUES (3, '王五', 72, 95, 60);
如果要查每个学生三科中的最高分,直接使用GREATEST即可:
SELECT
name,
chinese,
math,
english,
GREATEST(chinese, math, english) AS max_score
FROM student_score;
这条查询会为每一行返回一个最大值,比如张三的结果是92,李四是90。写法非常简洁,一行SQL就搞定了。
但是这里有一个非常经典的坑:NULL值的处理。在MySQL和Oracle中,只要GREATEST的任意一个参数为NULL,整个结果就会直接返回NULL。比如某个学生的英语成绩缺考记为NULL,那么他的三科最高分就会变成NULL,这显然不符合业务预期。解决方法是用COALESCE或IFNULL把NULL先替换成一个极小值:
SELECT
name,
GREATEST(
COALESCE(chinese, 0),
COALESCE(math, 0),
COALESCE(english, 0)
) AS max_score
FROM student_score;
这样即使某科缺考,也能正确算出其他科目中的最高分。需要注意的是,PostgreSQL的行为和MySQL不同,PostgreSQL的GREATEST会直接忽略NULL参数,只比较非NULL的值,只有当所有参数都是NULL时才返回NULL。所以跨数据库迁移时要特别留意这个差异。
三、跨行又跨列:聚合函数与GREATEST的组合技巧
实际业务中还有一种更复杂的需求:既要跨列比较,又要跨行聚合。比如要查整张表中所有学生、所有科目里的全局最高分,就可以把MAX和GREATEST嵌套起来用:
SELECT MAX(GREATEST(chinese, math, english)) AS global_max FROM student_score;
这个写法的执行逻辑分两步:先由内层的GREATEST算出每个学生的个人最高分,再由外层的MAX在这些个人最高分中挑出最大的一个。内外层各司其职,语义非常清晰。
再进一步,如果既要看每个班级的最高分,又要看全局排名,可以结合GROUP BY使用:
SELECT
class_id,
MAX(GREATEST(chinese, math, english)) AS class_max
FROM student_score
GROUP BY class_id;
这种嵌套组合的思路可以推广到很多场景,比如取每个订单在多个渠道价格中的最低价(用LEAST),再统计全局最低价的平均值等等。核心技巧就是:标量函数负责行内运算,聚合函数负责行间汇总,两者按照执行顺序正确嵌套即可。
四、SQL Server等不支持GREATEST的替代方案
GREATEST虽然好用,但并不是所有数据库都支持。老版本的SQL Server(2022之前)就没有这个函数,SQL Server 2022才开始原生支持。在不支持的情况下,有几种常见的替代写法。
第一种是使用CASE WHEN逐个比较:
SELECT
name,
CASE
WHEN chinese >= math AND chinese >= english THEN chinese
WHEN math >= english THEN math
ELSE english
END AS max_score
FROM student_score;
这种写法兼容性最好,任何数据库都能跑,缺点是列数多的时候条件会变得很长,维护起来比较麻烦,而且要小心处理NULL参与比较时的三值逻辑问题。
第二种是利用UNPIVOT或者行转列的思路,把多列数据转成多行,再用MAX聚合:
SELECT name, MAX(score) AS max_score
FROM (
SELECT name, chinese AS score FROM student_score
UNION ALL
SELECT name, math AS score FROM student_score
UNION ALL
SELECT name, english AS score FROM student_score
) t
GROUP BY name;
这种写法的好处是逻辑统一,无论多少列都是同样的模式扩展,而且天然规避了NULL的问题(NULL在MAX聚合中会被忽略)。缺点是子查询会扫描多次表,数据量大时性能不如GREATEST。
最后给一个选择建议:如果数据库支持GREATEST,优先用它,简洁高效;遇到NULL记得配合COALESCE处理;如果需要跨库兼容或者列数不固定,再考虑UNION ALL转行的方案。掌握这几个技巧之后,多列最大值的查询就再也没有难点了。
SQL多列最大值GREATEST函数聚合函数修改时间:2026-09-07 02:44:27