SQL 查询不保证返回结果的顺序。如果我们想要按照某种规则对结果进行排序显示,例如按照工资从高到低进行排序,需要使用 ORDER BY 子句。
本文比较五种主流数据库对查询结果排序的实现和差异,包括 MySQL、Oracle、SQL Server、PostgreSQL 以及 SQLite。
ORDER BY | MySQL | Oracle | SQL Server | PostgreSQL | SQLite |
---|---|---|---|---|---|
单个字段排序 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
多个字段排序 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
表达式排序 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
空值排序 | 默认最小 | 默认最大 | 默认最小 | 默认最大 | 默认最小 |
中文排序 | 偏旁部首、拼音 | 偏旁部首、拼音、笔画 | 偏旁部首、拼音、笔画 | 偏旁部首、拼音 | 偏旁部首 |
基于单个字段排序
基于单个字段值的排序操作被称为单列排序。单列排序的语法如下:
sql
SELECT col1, col2, ...
FROM t
[WHERE ...]
ORDER BY col1 [ASC | DESC];
其中,ORDER BY 子句用于指定排序,ASC 表示按照升序排序(Ascending),DESC 表示按照降序排序(Descending),默认按照升序排序。
以下语句查找公司的女性员工,并且按照月薪从高到低排序显示:
sql
SELECT emp_name, salary
FROM employee
WHERE sex = '女'
ORDER BY salary DESC;
查询返回的结果如下:
sql
emp_name|salary
--------|--------
孙尚香 |12000.00
赵氏 | 6600.00
孙丫鬟 | 6000.00
提示:对于升序排序,数字按照从小到大的顺序排列,字符按照编码的顺序排列,日期和时间按照从早到晚的顺序排列;对于降序排序则正好相反。
基于多个字段排序
如果排序字段中存在相同的数据,那么它们的排序顺序是随机的。为了进一步明确这些数据的排序顺序,可以使用多列排序。
多列排序指的是基于多个字段值的排序,多个字段间使用逗号进行分隔。多列排序的语法如下:
sql
SELECT col1, col2, ...
FROM t
[WHERE ...]
ORDER BY col1 [ASC | DESC], col2 [ASC | DESC], ...;
首先,查询基于第一个字段进行排序,对于第一个字段排序相同的数据,再基于第二个字段进行排序,并且依此类推。
以下语句查找销售部(dept_id=5)的员工信息,并且按照月薪从高到低排序,如果其月薪相同,则按照入职先后进行排序:
sql
SELECT emp_name, salary, hire_date
FROM employee
WHERE dept_id = 5
ORDER BY salary DESC, hire_date;
查询返回的结果如下:
sql
emp_name|salary |hire_date
--------|--------|----------
法正 |10000.00|2017-04-09
简雍 | 4800.00|2019-05-11
...
蒋琬 | 4000.00|2018-01-28
邓芝 | 4000.00|2018-11-11
其中,"蒋琬"和"邓芝"的月薪相同,但是"蒋琬"排在了"邓芝"之前,因为他的入职日期更早。
基于表达式排序
除了基于字段的值进行排序外,我们也可以基于表达式的值进行排序。例如,以下语句查找行政管理部(dept_id=1)的员工,并且按照全年总收入进行排序:
sql
SELECT emp_name, salary * 12 + bonus
FROM employee
WHERE dept_id = 1
ORDER BY salary * 12 + bonus;
员工的全年总收入等于年薪(salary*12)加奖金(bonus),查询返回的结果如下:
sql
emp_name|salary * 12 + bonus
--------|-------------------
张飞 | 298000.00
关羽 | 322000.00
刘备 | 370000.00
另外,我们也可以使用字段或者表达式在 SELECT 列表中出现的位置来指定数据的排序。
例如,上面的查询语句可以改写如下:
sql
SELECT emp_name, salary * 12 + bonus
FROM employee
WHERE dept_id = 1
ORDER BY 2;
表达式 salary * 12 + bonus 是查询返回的第 2 列,因此 ORDER BY 2 也表示按照全年总收入进行排序。
空值的排序位置
空值(NULL)在数据库中表示未知或者缺失的数据。如果排序的字段中存在空值时,应该如何处理呢?以下语句查找人力资源部(dept_id=2)中的员工,并且按照奖金从低到高进行排序显示:
sql
SELECT emp_name, bonus
FROM employee
WHERE dept_id = 2
ORDER by bonus;
不同数据库系统对于空值的排序位置采用了不同的处理方式。MySQL、Microsoft SQL Server 以及 SQLite 中的空值排在了最前,查询返回的结果如下:
sql
-- MySQL、Microsoft SQL Server 以及 SQLite
emp_name|bonus
--------|-------
黄忠 |
魏延 |
诸葛亮 |8000.00
Oracle 和 PostgreSQL 中的空值排在了最后,查询返回的结果如下:
sql
-- Oracle 以及 PostgreSQL
emp_name|bonus
--------|-------
诸葛亮 |8000.00
黄忠 |
魏延 |
另外,Oracle、PostgreSQL 以及 SQLite 支持使用 NULLS FIRST 关键字,将空值排在最前;或者使用 NULLS LAST 关键字,将空值排在最后。以下查询语句返回的结果与上面的 MySQL 和 Microsoft SQL Server 一致:
sql
-- Oracle、PostgreSQL 以及 SQLite
SELECT emp_name, bonus
FROM employee
WHERE dept_id = 2
ORDER by bonus NULLS FIRST;
总而言之,对于空值的排序:
- MySQL、Microsoft SQL Server 以及 SQLite 认为排序时空值最小,升序排序时空值排在最前,降序排序时空值排在最后。
- Oracle 和 PostgreSQL 认为排序时空值最大,升序排序时空值排在最后,降序排序时空值排在最前。
- Oracle、PostgreSQL 以及 SQLite 支持使用 NULLS FIRST 和 NULLS LAST 指定空值的排序位置。
中文的排序方式
在创建数据库或者表时,我们通常会指定一个字符集和排序规则。字符集(Charset)决定了数据库能够存储哪些字符,比如 ASCII 字符集只能存储简单的英文、数字和一些控制字符,GB2312 字符集可以存储中文,Unicode 字符集能够支持世界上的各种文字。
排序规则(Collation)定义了字符集中字符的排序顺序,包括是否区分大小写、是否区分重音等。对于中文而言,排序方式与英文有所不同,中文通常需要按照拼音、偏旁部首或者笔画进行排序。
如果想要支持中文排序,最简单的方式就是使用支持中文排序的字符集和排序规则。如果使用的字符集和排序规则不满足我们的排序需求,可以通过其他方法实现。
Oracle 默认使用 AL32UTF8 字符编码,中文按照偏旁部首进行排序。我们可以通过一个转换函数实现其他方式的中文排序,以下查询按照员工姓名的拼音进行排序:
sql
-- Oracle 实现中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY NLSSORT(emp_name,'NLS_SORT = SCHINESE_PINYIN_M');
其中,NLSSORT()是 Oracle 提供的一个系统函数,用于返回按照指定排序规则编码的字符序列,SCHINESE_PINYIN_M 表示中文的拼音排序规则。查询返回的结果如下:
sql
EMP_NAME
--------
关平
关兴
廖化
马岱
张苞
赵氏
赵统
赵云
周仓
除按照拼音排序外,Oracle 还支持按照偏旁部首(SCHINESE_RADICAL_M)以及笔画(SCHINESE_STROKE_M)进行中文排序。
MySQL 8.0 默认使用 utf8mb4 字符编码,中文按照偏旁部首进行排序。我们可以通过一个转换函数实现其他方式的中文排序,以下查询按照员工姓名的拼音进行排序:
sql
-- MySQL 实现中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY CONVERT(emp_name USING GBK);
其中,CONVERT()是一个 MySQL 系统函数,用于转换数据的字符集编码,中文 GBK 字符集默认使用拼音进行排序。查询返回的结果和上面的 Oracle 示例相同。
Microsoft SQL Server 中的字符集和排序规则是同一个概念,安装数据库时默认根据操作系统所在的区域进行设置,中国地区默认使用 Chinese_PRC_CI_AS 排序规则,中文按照偏旁部首进行排序。我们可以通过 COLLATE 关键字实现其他方式的中文排序,以下查询按照员工姓名的拼音进行排序:
sql
-- SQL Server 实现中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY emp_name COLLATE Chinese_PRC_CI_AI_KS_WS;
其中,COLLATE 表示按照某种排序规则进行排序,Chinese_PRC_CI_AI_KS_WS 表示中文拼音排序规则。查询返回的结果和上面的 Oracle 示例一样。
Microsoft SQL Server 也支持中文按照笔画进行排序(Chinese_PRC_Stroke_CI_AS)。
PostgreSQL 默认使用 UTF-8 编码字符集,中文按照偏旁部首进行排序。我们可以通过 COLLATE 关键字实现其他方式的中文排序,以下查询按照员工姓名的拼音进行排序:
sql
-- PostgreSQL 实现中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY emp_name COLLATE "zh_CN";
其中,COLLATE 表示按照某种排序规则进行排序,zh_CN 表示中文拼音排序规则。查询返回的结果和上面的 Oracle 示例一样。
SQLite 默认使用 UTF-8 字符编码,中文按照偏旁部首进行排序,不支持其他的排序方式。