ORDER BY 的四十年:从只能排 SELECT 列到 NULLS FIRST,SQL 排序语法的暗坑与进化

2026-05-15 44 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:9 分钟

每个写 SQL 的人都用过 ORDER BY,但很少有人意识到:今天你随手写的 ORDER BY created_at DESC NULLS LAST,在 1980 年代的 SQL 标准里根本不存在。Markus Winand 最近的文章梳理了 ORDER BY 从诞生到现代的完整演进,揭示了这个"最简单语法"背后层层叠加的历史包袱。理解这段进化史,能帮你避开几个真实存在的排序暗坑。

最早的 ORDER BY:只能按 SELECT 列里的序号排

SQL-86 和 SQL-89 时代的 ORDER BY 能力极其有限。它只允许你用两种方式指定排序列:

  1. 列名——但必须是 SELECT 列表里出现的列。
  2. 列序号——用数字指代 SELECT 列表中的第几列。
-- SQL-89 合法写法:用序号排序
SELECT employee_id, last_name, hire_date
  FROM employees
 ORDER BY 3;            -- 按 hire_date 排序

-- SQL-89 合法写法:用 SELECT 列里的列名
SELECT employee_id, last_name, hire_date
  FROM employees
 ORDER BY hire_date;

这意味着两个关键限制:

  • 不能按一个不在 SELECT 里的列排序。想按 salary 排序但不想输出工资?不行,必须把 salary 加进 SELECT
  • 不能按表达式排序。ORDER BY UPPER(last_name) 在早期标准里是非法的。

用列序号排序在今天仍然被多数数据库支持,但这是一个公认的代码可读性陷阱——加一列到 SELECT,排序逻辑就悄悄变了。现代工程实践基本建议避免这种写法。

SQL-92 打破围墙:允许引用不在 SELECT 里的列

SQL-92 是 ORDER BY 的第一次重大升级。标准终于允许排序引用不在 SELECT 列表中的列,也允许使用表达式

-- SQL-92 合法:按不在 SELECT 里的列排序
SELECT employee_id, last_name
  FROM employees
 ORDER BY salary DESC;   -- salary 不在 SELECT 里,但可以排序

-- SQL-92 合法:按表达式排序
SELECT employee_id, last_name
  FROM employees
 ORDER BY UPPER(last_name);

这对实际开发影响很大。你终于可以写"只返回姓名,但按工资排序"的查询了,不再被迫把排序列暴露给调用方。

但 SQL-92 仍然有一个巨大空白:NULL 值的排序顺序没有定义。标准只说 NULL 排在"所有值的前面或后面",具体哪种由实现决定。这直接导致了长达十年的跨数据库不一致问题。

NULL 排序的混乱十年

在 SQL-92 到 SQL:2003 之间,各数据库对 NULL 的排序顺序各行其是:

数据库 NULL 在 ASC 中的位置 NULL 在 DESC 中的位置
PostgreSQL 最后 最前
MySQL 最前 最后
Oracle 最后 最前
SQL Server 最前 最后

同样的查询 ORDER BY score ASC,PostgreSQL 把 NULL 行扔到结果末尾,MySQL 把 NULL 行放在最前面。如果你的应用从 MySQL 迁移到 PostgreSQL,排序结果会悄悄改变——没有报错,没有警告,只是数据顺序不一样。

SQL:2003 终于标准化了 NULLS FIRSTNULLS LAST

-- SQL:2003 标准语法
SELECT employee_id, bonus
  FROM employees
 ORDER BY bonus ASC NULLS LAST;   -- NULL 排在最后,不管 ASC 还是 DESC

但现实是:MySQL 和 SQL Server 至今不支持 NULLS FIRST/LAST。要在这两个数据库里控制 NULL 顺序,你得用变通方案:

-- MySQL 变通:用 IS NULL 制造排序权重
SELECT employee_id, bonus
  FROM employees
 ORDER BY bonus IS NULL, bonus DESC;
-- bonus IS NULL 返回 0 或 1,NULL 行被推到后面

-- SQL Server 变通:用 CASE
SELECT employee_id, bonus
  FROM employees
 ORDER BY CASE WHEN bonus IS NULL THEN 1 ELSE 0 END, bonus DESC;

这段历史直接告诉你:如果你的系统可能跨数据库迁移,排序逻辑必须显式声明 NULL 顺序,不能依赖默认行为。

现代 ORDER BY:窗口函数中的排序与表达式排序

SQL:2003 引入窗口函数后,ORDER BY 的角色扩展了——它不仅出现在查询末尾,还出现在 OVER () 子句里,语义完全不同:

-- 查询级 ORDER BY:决定最终输出顺序
SELECT employee_id, salary
  FROM employees
 ORDER BY salary DESC;

-- 窗口级 ORDER BY:决定窗口函数的计算顺序,不影响输出顺序
SELECT employee_id,
       salary,
       ROW_NUMBER() OVER (ORDER BY salary DESC) AS rank
  FROM employees
 ORDER BY employee_id;   -- 最终输出按 employee_id,不按 salary

这两个 ORDER BY 互不干扰。窗口里的 ORDER BY 只管帧的范围和计算顺序,查询末尾的 ORDER BY 只管输出排列。混淆两者是新手写窗口函数时最常见的错误。

现代数据库还支持更复杂的排序表达式:

-- PostgreSQL:按数组元素排序
SELECT *
  FROM reports
 ORDER BY tags[1];

-- PostgreSQL:按文本中的数字排序(需要扩展函数)
SELECT *
  FROM versions
 ORDER BY string_to_array(version_text, '.')::int[];
-- '2.1.3' → [2,1,3],实现语义化版本排序

实战检查清单

理解了 ORDER BY 的进化史后,以下几条规则可以直接用在日常开发中:

1. 永远不用列序号排序

-- ❌ 避免
SELECT id, name, age FROM users ORDER BY 3;

-- ✅ 改用列名
SELECT id, name, age FROM users ORDER BY age;

列序号排序在 SELECT 列表变更时会产生静默错误,且可读性差。

2. 显式声明 NULL 排序方向

-- ❌ 依赖数据库默认行为
SELECT id, score FROM players ORDER BY score DESC;

-- ✅ 显式指定(PostgreSQL / Oracle / SQLite)
SELECT id, score FROM players ORDER BY score DESC NULLS LAST;

-- ✅ MySQL / SQL Server 变通
SELECT id, score FROM players
 ORDER BY score IS NULL, score DESC;

3. 区分窗口 ORDER BY 和查询 ORDER BY

-- ❌ 以为窗口 ORDER BY 会决定输出顺序
SELECT id, salary,
       RANK() OVER (ORDER BY salary DESC) AS r
  FROM employees;
-- 输出顺序不确定!没有查询级 ORDER BY

-- ✅ 加上查询级 ORDER BY
SELECT id, salary,
       RANK() OVER (ORDER BY salary DESC) AS r
  FROM employees
 ORDER BY r;

4. 需要按非 SELECT 列排序时,确认数据库支持

SQL-92 标准允许,但某些数据库配置(如 MySQL 的 sql_mode=ONLY_FULL_GROUP_BY 相关严格模式)可能限制这种行为。测试你的环境:

-- 测试:按不在 SELECT 中的列排序是否合法
SELECT name FROM employees ORDER BY salary;
-- 如果报错,说明你的数据库配置比标准更严格

ORDER BY 从"只能排 SELECT 列、只能用列序号"到"表达式排序、NULLS FIRST/LAST、窗口函数排序",四十年里每次升级都在修补前一代留下的模糊地带。这些修补没有完全统一——MySQL 和 SQL Server 仍然缺少 NULLS FIRST/LAST,列序号排序仍然被支持但被公认应该避免。写排序语句时,最安全的做法是:永远显式声明你的意图,不依赖任何默认行为


相关推荐