mysql中null默认最小,asc排最前、desc排最后;统一置后应使用order by (字段 is null), 字段 asc,利用布尔值0/1升序实现非null优先、null垫底。

MySQL中用ORDER BY ... IS NULL把NULL放末尾
MySQL默认把NULL当最小值,ASC时排最前,DESC时反而在最后——这常和直觉相反。想统一让NULL始终靠后,得显式控制其排序权重。
最稳妥的做法是把判断逻辑塞进ORDER BY子句:
SELECT * FROM users ORDER BY (age IS NULL), age ASC;
age IS NULL返回0(非空)或1(为空),先按这个布尔结果升序排,0在前、1在后;再按age本身升序。这样所有非NULL的age先排,NULL全被压到末尾。
- 别用
ORDER BY IFNULL(age, 999999)这类“补值”法——可能和真实数据冲突,且影响索引使用 -
IS NULL表达式在ORDER BY里可直接用,无需额外CASE,更轻量 - 如果字段类型是字符串,同样适用:
(name IS NULL), name ASC
PostgreSQL用NULLS LAST语法最干净
PostgreSQL原生支持NULLS LAST(或NULLS FIRST),写法直观,语义明确:
SELECT * FROM products ORDER BY price ASC NULLS LAST;
这条语句不管price是数字还是时间戳,只要定义了ASC,加上NULLS LAST就强制NULL垫底。
-
NULLS LAST必须紧跟在排序方向之后,不能写成ORDER BY price NULLS LAST ASC(顺序错会报错) - 多个字段混合排序时,每个字段可独立指定:
ORDER BY category ASC NULLS FIRST, price DESC NULLS LAST - 注意:旧版本PostgreSQL(NULLS LAST在索引中的优化有限,高频查询建议配合函数索引
SQL Server和Oracle要用CASE模拟
SQL Server和Oracle不支持NULLS LAST语法,也缺少IS NULL在ORDER BY里的布尔排序能力,只能靠CASE给NULL打标记:
SELECT * FROM orders ORDER BY CASE WHEN amount IS NULL THEN 1 ELSE 0 END, amount ASC;
原理和MySQL类似:先按CASE结果分组(非NULL为0,NULL为1),再按字段本身排序。
- 避免在
CASE里返回字符串(如'null'),否则会触发隐式转换,拖慢排序 - SQL Server中若字段允许
NULL且建了索引,这个CASE表达式大概率无法走索引,大数据量时需评估性能 - Oracle用户注意:
CASE表达式在ORDER BY里会被计算多次,尽量保持简单,别嵌套子查询
跨数据库兼容写法存在但代价高
真要写一条在MySQL/PostgreSQL/SQL Server都生效的SQL,只能退回到最保守的CASE方案,并接受它在各平台都略显冗余:
SELECT * FROM logs ORDER BY CASE WHEN created_at IS NULL THEN 1 ELSE 0 END, created_at ASC;
这个写法没用任何方言特性,但问题也很明显:
- 所有数据库都会执行两次
created_at IS NULL判断(一次进CASE,一次进排序),CPU开销翻倍 - 无法利用
created_at字段上的普通B-tree索引,因为排序依赖的是表达式结果,不是原始列 - 如果表有上亿行,又没加覆盖索引,
ORDER BY阶段容易撑爆tempdb或work_mem
实际项目里,与其强求一行通吃,不如在DAO层或ORM里根据方言动态拼接排序逻辑——毕竟NULL位置是业务语义的一部分,不是纯技术开关。











