SQL 数据分析 · 指南 · 2026年10月30日 · 13 分钟阅读

SQL ORDER BY 排序:升序降序、多字段排序、NULLS LAST 与取前 N 条

跑道上的飞机从小到大排成一列,每架旁边都有编号标记

ORDER BY 给查询结果排序。把它写在查询的末尾,列出要排序的列,每列后面加上 ASC(升序,默认)或 DESC(降序):ORDER BY range DESC 会把航程最长的机型排在最前面。多字段排序时,第二列只在第一列相同时才起作用。没有 ORDER BY,数据库返回的行没有任何保证的顺序,所以凡是关心顺序的查询,以及每一个带 LIMIT 的查询,都需要它。最容易踩坑的两个细节:NULL 排在哪里(各数据库不同,所以要写 NULLS FIRST 或 NULLS LAST),以及取前 N 条时边界上的并列。

下面的例子都在 Nomto SQL 课程的航空公司数据库上运行,使用 PostgreSQL 语法,引擎是 DuckDB(Nomto SQL 练习场背后的引擎),每个结果表都是真实输出。

ORDER BY 语法

SELECT columns
FROM table
WHERE condition
ORDER BY column1 [ASC | DESC] [NULLS FIRST | NULLS LAST],
         column2 [ASC | DESC] ...
LIMIT n;

ORDER BY 写在 WHERE、GROUP BY 和 HAVING 之后,LIMIT 之前。它也是数据库最后执行的步骤之一,所以可以使用 SELECT 里定义的别名(后面会讲)。

升序和降序

九种机型,按航程从短到长排序:

SELECT model, range
FROM aircrafts
ORDER BY range;
model range
Cessna 208 Caravan 1200
Bombardier CRJ-200 2700
Sukhoi Superjet-100 3000
Boeing 737-300 4200
Airbus A321-200 5600
Airbus A320-200 5700
Airbus A319-100 6700
Boeing 767-300 7900
Boeing 777-300 11100

ORDER BY range 等同于 ORDER BY range ASC。要最长的排在前面,就写 ORDER BY range DESC。数字按大小排,日期和时间从早到晚排,文本按字母顺序排(有几个需要注意的地方,见「文本排序」一节)。

多字段排序

用逗号分隔多个列。行先按第一列排序;只有第一列相同时才看第二列,依此类推。每一列有自己的方向。按制造商从 A 到 Z 分组,同一制造商内部按航程从长到短:

SELECT split_part(model, ' ', 1) AS maker, model, range
FROM aircrafts
ORDER BY maker, range DESC;
maker model range
Airbus Airbus A319-100 6700
Airbus Airbus A320-200 5700
Airbus Airbus A321-200 5600
Boeing Boeing 777-300 11100
Boeing Boeing 767-300 7900
Boeing Boeing 737-300 4200
Bombardier Bombardier CRJ-200 2700
Cessna Cessna 208 Caravan 1200
Sukhoi Sukhoi Superjet-100 3000

一个常见误区:写了 ORDER BY maker, range DESC,以为两列都是降序。DESC 只作用于紧挨在它前面的那一列,这里的 maker 仍然是升序。

按别名、列序号或表达式排序

ORDER BY 后面可以写:

对分组结果按聚合值排序,用别名或列序号都可以:

SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY flights DESC;   -- 或 ORDER BY 2 DESC,或 ORDER BY COUNT(*) DESC
status flights
Arrived 16707
Scheduled 15383
On Time 518
Cancelled 414
Departed 58
Delayed 41

列序号(ORDER BY 2)在探索数据时很顺手,但在保存下来的查询里很脆弱:在 SELECT 开头加一列,排序就悄悄变了。别人要读的查询里,最好写列名。

还有两个限制。别名可以单独使用,但不一定能放进表达式里:当 flights 是别名时,PostgreSQL 会拒绝 ORDER BY flights * 2。另外,用 SELECT DISTINCT 时,PostgreSQL 要求 ORDER BY 里的每个表达式都出现在选择列表中(报错「for SELECT DISTINCT, ORDER BY expressions must appear in select list」);DuckDB 宽松一些,但别依赖这一点。

用 CASE 自定义排序

按字母排状态没有意义,大家想看的是航班生命周期的顺序。用 CASE 给每个值一个编号,再按编号排序:

SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY CASE status
           WHEN 'Scheduled' THEN 1
           WHEN 'On Time'   THEN 2
           WHEN 'Delayed'   THEN 3
           WHEN 'Departed'  THEN 4
           WHEN 'Arrived'   THEN 5
           ELSE 6
         END;
status flights
Scheduled 15383
On Time 518
Delayed 41
Departed 58
Arrived 16707
Cancelled 414

ELSE 让任何意料之外的值排到最后,而不是最前。同样的技巧可以把某个值置顶:ORDER BY city = 'Moscow' DESC, city 先列出 Moscow,其余按字母排(在 PostgreSQL 和 DuckDB 里 true 排在 false 之后,所以要用 DESC)。MySQL 还有 ORDER BY FIELD(status, 'Scheduled', 'On Time', ...),这是 MySQL 特有的写法,在国内项目里很常见,但换到别的数据库就用不了。更多 CASE 用法见 SQL CASE WHEN 用法。

NULLS FIRST 和 NULLS LAST

哪些航班起飞延误最久?有 16,348 个航班还没起飞,它们的 actual_departure 以及算出来的延误时间都是 NULL:

SELECT flight_no, departure_airport, scheduled_departure,
       EXTRACT(EPOCH FROM actual_departure - scheduled_departure) / 60 AS delay_min
FROM flights
ORDER BY delay_min DESC NULLS LAST
LIMIT 5;
flight_no departure_airport scheduled_departure delay_min
PG0589 PEE 2026-07-25 15:30:00 277
PG0164 DME 2026-07-25 15:25:00 268
PG0364 KRR 2026-07-15 11:45:00 267
PG0568 OVS 2026-08-09 16:15:00 260
PG0096 URJ 2026-07-30 16:35:00 258

改成 NULLS FIRST,排在最前面的就是根本没有延误数据的航班:

flight_no departure_airport scheduled_departure delay_min
PG0402 DME 2026-09-05 12:25:00 NULL
PG0405 DME 2026-08-12 09:35:00 NULL
PG0404 DME 2026-08-12 19:05:00 NULL

如果两者都不写,结果取决于数据库:

数据库 ASC 时 NULL 在 DESC 时 NULL 在
PostgreSQL、Oracle 最后 最前
DuckDB 最后 最后
MySQL、SQL Server、SQLite、Hive、Spark SQL 最前 最后

所以同一条 ORDER BY delay_min DESC LIMIT 5,在 DuckDB 里返回真正的前 5 名,在 PostgreSQL 里却返回五个 NULL。PostgreSQL、Oracle、DuckDB、Spark SQL、Hive(2.1 起)和 SQLite(3.30 起)都支持 NULLS FIRST/NULLS LAST。MySQL 和 SQL Server 不支持;在那里要先按一个标记排序:MySQL 写 ORDER BY delay_min IS NULL, delay_min DESC,SQL Server 写 ORDER BY CASE WHEN delay_min IS NULL THEN 1 ELSE 0 END, delay_min DESC。NULL 的更多行为见 SQL NULL 值处理。

ORDER BY 加 LIMIT:取前 N 条

ORDER BY 加 LIMIT 就是取「前 N 名」的方法。从谢列梅捷沃机场(SVO)出发,航班最多的四个目的地:

SELECT arrival_airport, COUNT(*) AS flights
FROM flights
WHERE departure_airport = 'SVO'
GROUP BY arrival_airport
ORDER BY flights DESC, arrival_airport
LIMIT 4;
arrival_airport flights
LED 305
BZK 183
GOJ 122
PES 122

边界上的并列

GOJ、PES 和 ULV 三个机场都是 122 个航班,而前 4 名里只放得下两个。如果去掉第二排序列 arrival_airport,数据库可以随便挑其中两个,它也确实这么做了:只用 ORDER BY flights DESC 把查询运行五次,第 3、第 4 名得到了五种不同的组合(PES 和 ULV、GOJ 和 PES、ULV 和 PES、GOJ 和 ULV、PES 和 GOJ)。每次刷新都变的报表就是一个 bug。两种解决办法:

  1. 加一个决胜列,让顺序唯一,就像上面那样。结果稳定了,但截断仍然是任意的:ULV 落选只是因为它的名字。
  2. 保留所有并列的行。 PostgreSQL 13+ 和 Oracle 支持 FETCH FIRST 4 ROWS WITH TIES,SQL Server 有 TOP 4 WITH TIES。DuckDB 和 MySQL 都没有,用 RANK():
SELECT arrival_airport, flights
FROM (
  SELECT arrival_airport, COUNT(*) AS flights,
         RANK() OVER (ORDER BY COUNT(*) DESC) AS rnk
  FROM flights
  WHERE departure_airport = 'SVO'
  GROUP BY arrival_airport
) t
WHERE rnk <= 4
ORDER BY flights DESC, arrival_airport;
arrival_airport flights
LED 305
BZK 183
GOJ 122
PES 122
ULV 122

五行,因为三个机场并列第 3。每组取前 N 条(比如每个机场的前 3 条航线)见 SQL 窗口函数;RANK、DENSE_RANK 和 ROW_NUMBER 在并列时的区别见 ROW_NUMBER、RANK 和 DENSE_RANK。

各数据库怎么取前 N 条

数据库 取前 3 条
PostgreSQL、DuckDB、MySQL、SQLite、Hive、Spark SQL ORDER BY range DESC LIMIT 3
SQL Server SELECT TOP 3 ... ORDER BY range DESC
标准 SQL(PostgreSQL、Oracle 12c+、SQL Server、DuckDB) ORDER BY range DESC FETCH FIRST 3 ROWS ONLY

用 OFFSET 分页

OFFSET 在 LIMIT 开始计数之前跳过若干行。机型列表的第 2 页,每页三条:

SELECT model, range
FROM aircrafts
ORDER BY range DESC
LIMIT 3 OFFSET 3;
model range
Airbus A320-200 5700
Airbus A321-200 5600
Boeing 737-300 4200

标准写法 OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY 返回同样的行,在 SQL Server 里也能用(那里 OFFSET 必须配合 ORDER BY)。MySQL 还有简写 LIMIT 3, 3(先偏移量,后行数,顺序和 LIMIT 3 OFFSET 3 相反,容易写反)。分页只有在顺序唯一时才有意义:有并列时,某一行可能出现在两页上,也可能哪一页都没有。在大表上,很深的偏移量很慢,因为数据库仍然要生成并跳过前面所有的行;应用程序通常改用「游标分页」(keyset pagination,WHERE range < last_seen_value ORDER BY range DESC LIMIT 3)。

文本排序:存成文本的数字、大小写和中文

文本按字符逐个比较,所以存成文本的数字会排「错」。空客 A319 的座位号是 1A、10C 这样的文本:

SELECT seat_no FROM seats WHERE aircraft_code = '319' ORDER BY seat_no LIMIT 8;
seat_no
10A
10B
10C
10D
10E
10F
11A
11B

第 10 排排在第 1 排前面,因为「1」后面跟「0」比「1」后面跟「A」小。先按数字部分排,再按字母排:

SELECT seat_no, fare_conditions
FROM seats
WHERE aircraft_code = '319'
ORDER BY CAST(regexp_extract(seat_no, '^[0-9]+') AS INTEGER), right(seat_no, 1)
LIMIT 8;
seat_no fare_conditions
1A Business
1C Business
1D Business
1F Business
2A Business
2C Business
2D Business
2F Business

在 PostgreSQL 里,把 regexp_extract(seat_no, '^[0-9]+') 换成 substring(seat_no FROM '^[0-9]+')。以文本形式导入的 ID 和金额也有同样的问题:'10' < '9'。真正的解决办法是把它们存成数字类型。

大小写是另一个意外。DuckDB 按字符编码比较文本,所以所有大写字母都排在所有小写字母前面:

SELECT x FROM (VALUES ('banana'), ('Cherry'), ('apple'), ('Apple')) t(x)
ORDER BY x;
x
Apple
Cherry
apple
banana

PostgreSQL 在常见的 en_US 排序规则下返回 apple、Apple、banana、Cherry;MySQL 和 SQL Server 默认的不区分大小写的排序规则会把 apple 和 Apple 视为相等。要在所有地方得到同样的顺序,就按 lower(x) 排序,再用 x 作决胜列:

x
Apple
apple
banana
Cherry

中文按拼音排序

中文也是同样的问题:默认按字符编码(Unicode 码位)排序,结果既不是拼音顺序,也不是笔画顺序:

SELECT x FROM (VALUES ('上海'), ('北京'), ('广州'), ('深圳')) t(x)
ORDER BY x;
x
上海
北京
广州
深圳

在 DuckDB 里指定中文排序规则,就会按拼音排:

SELECT x FROM (VALUES ('上海'), ('北京'), ('广州'), ('深圳')) t(x)
ORDER BY x COLLATE zh;
x
北京
广州
上海
深圳

其他数据库的写法:PostgreSQL(带 ICU 支持)用 ORDER BY x COLLATE "zh-x-icu";MySQL 8 可以用 ORDER BY x COLLATE utf8mb4_zh_0900_as_cs,老项目里常见的写法是 ORDER BY CONVERT(x USING gbk)(GBK 编码里常用汉字大致按拼音排列,生僻字不一定准)。

随机排序

要随机挑几行,就按随机数排序:

SELECT member_id, signup_channel
FROM loyalty_members
ORDER BY random()
LIMIT 3;
member_id signup_channel
781 web
32855 web
36422 airport

你运行时得到的三行会不一样,这正是目的。这个函数在 PostgreSQL、DuckDB 和 SQLite 里是 random(),在 MySQL、Hive 和 Spark SQL 里是 RAND(),SQL Server 用 ORDER BY NEWID()。为了留下三行而把整张大表排一遍代价很高;要从大表抽样,PostgreSQL 和 DuckDB 提供 TABLESAMPLE(DuckDB 还有 USING SAMPLE)。

常见错误

这些规则在 SQL 基础查询和 SQL 速查表里也会反复出现。

常见问题

SQL ORDER BY 默认是升序还是降序?

默认升序(ASC):最小的数字、最早的日期、字母表开头的值排在前面。DESC 反过来。NULL 默认排在哪里取决于数据库,所以用 NULLS FIRST 或 NULLS LAST 明确写出来。

ORDER BY 怎样按多个字段排序?

用逗号分隔,每列写自己的方向:ORDER BY maker ASC, range DESC。第二列只决定第一列相同的那些行之间的顺序。

ORDER BY 里能用列别名吗?

能。ORDER BY 在 SELECT 之后执行,所以 SELECT COUNT(*) AS flights ... ORDER BY flights DESC 在所有主流数据库里都能用。但 WHERE 里不能用别名,因为 WHERE 在 SELECT 之前执行。

怎样让 NULL 排在最后?

在列后面加 NULLS LAST:ORDER BY delay_min DESC NULLS LAST。MySQL 和 SQL Server 没有这个语法,要先按「是否为空」的标记排序,比如 MySQL 里写 ORDER BY delay_min IS NULL, delay_min DESC。

能按 SELECT 里没有的列排序吗?

能,除了 SELECT DISTINCT(PostgreSQL 和 SQL Server 里)以及 GROUP BY 之后已经不存在的列。在分组查询里,可以按分组列或像 COUNT(*) 这样的聚合值排序。

中文怎么按拼音排序?

指定中文排序规则:DuckDB 用 ORDER BY x COLLATE zh,PostgreSQL 用 COLLATE "zh-x-icu",MySQL 8 用 COLLATE utf8mb4_zh_0900_as_cs,或者用老办法 ORDER BY CONVERT(x USING gbk)。不指定时,中文按 Unicode 编码排序,看起来没有规律。

ORDER BY 会让查询变慢吗?

对大结果集排序要花时间,尤其是没有 LIMIT 的时候。有 ORDER BY ... LIMIT n 时,数据库会用 top-N 排序,只在内存里保留 n 行;在 PostgreSQL、MySQL 或 SQL Server 里,排序列上的索引还能让数据库完全省掉排序。

其他语言: English, Español, Português (Brasil), Tiếng Việt