五種主流數(shù)據(jù)庫:從無序到有序
SQL 查詢不保證返回結(jié)果的順序。如果我們想要按照某種規(guī)則對結(jié)果進行排序顯示,例如按照工資從高到低進行排序,需要使用 ORDER BY 子句。
本文比較五種主流數(shù)據(jù)庫對查詢結(jié)果排序的實現(xiàn)和差異,包括 MySQL、Oracle、SQL Server、PostgreSQL 以及 SQLite。
ORDER BY | MySQL | Oracle | SQL Server | PostgreSQL | SQLite |
單個字段排序 | ?? | ?? | ?? | ?? | ?? |
多個字段排序 | ?? | ?? | ?? | ?? | ?? |
表達式排序 | ?? | ?? | ?? | ?? | ?? |
空值排序 | 默認最小 | 默認最大 | 默認最小 | 默認最大 | 默認最小 |
中文排序 | 偏旁部首、拼音 | 偏旁部首、拼音、筆畫 | 偏旁部首、拼音、筆畫 | 偏旁部首、拼音 | 偏旁部首 |
基于單個字段排序
基于單個字段值的排序操作被稱為單列排序。單列排序的語法如下:
SELECT col1, col2, ...
FROM t
[WHERE ...]
ORDER BY col1 [ASC | DESC];
其中,ORDER BY 子句用于指定排序,ASC 表示按照升序排序(Ascending),DESC 表示按照降序排序(Descending),默認按照升序排序。
以下語句查找公司的女性員工,并且按照月薪從高到低排序顯示:
SELECT emp_name, salary
FROM employee
WHERE sex = '女'
ORDER BY salary DESC;
查詢返回的結(jié)果如下:
emp_name|salary
--------|--------
孫尚香 |12000.00
趙氏 | 6600.00
孫丫鬟 | 6000.00
提示:對于升序排序,數(shù)字按照從小到大的順序排列,字符按照編碼的順序排列,日期和時間按照從早到晚的順序排列;對于降序排序則正好相反。
基于多個字段排序
如果排序字段中存在相同的數(shù)據(jù),那么它們的排序順序是隨機的。為了進一步明確這些數(shù)據(jù)的排序順序,可以使用多列排序。
多列排序指的是基于多個字段值的排序,多個字段間使用逗號進行分隔。多列排序的語法如下:
SELECT col1, col2, ...
FROM t
[WHERE ...]
ORDER BY col1 [ASC | DESC], col2 [ASC | DESC], ...;
首先,查詢基于第一個字段進行排序,對于第一個字段排序相同的數(shù)據(jù),再基于第二個字段進行排序,并且依此類推。
以下語句查找銷售部(dept_id=5)的員工信息,并且按照月薪從高到低排序,如果其月薪相同,則按照入職先后進行排序:
SELECT emp_name, salary, hire_date
FROM employee
WHERE dept_id = 5
ORDER BY salary DESC, hire_date;
查詢返回的結(jié)果如下:
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)的員工,并且按照全年總收入進行排序:
SELECT emp_name, salary * 12 + bonus
FROM employee
WHERE dept_id = 1
ORDER BY salary * 12 + bonus;
員工的全年總收入等于年薪(salary*12)加獎金(bonus),查詢返回的結(jié)果如下:
emp_name|salary * 12 + bonus
--------|-------------------
張飛 | 298000.00
關(guān)羽 | 322000.00
劉備 | 370000.00
另外,我們也可以使用字段或者表達式在 SELECT 列表中出現(xiàn)的位置來指定數(shù)據(jù)的排序。
例如,上面的查詢語句可以改寫如下:
SELECT emp_name, salary * 12 + bonus
FROM employee
WHERE dept_id = 1
ORDER BY 2;
表達式 salary * 12 + bonus 是查詢返回的第 2 列,因此 ORDER BY 2 也表示按照全年總收入進行排序。
空值的排序位置
空值(NULL)在數(shù)據(jù)庫中表示未知或者缺失的數(shù)據(jù)。如果排序的字段中存在空值時,應該如何處理呢?以下語句查找人力資源部(dept_id=2)中的員工,并且按照獎金從低到高進行排序顯示:
SELECT emp_name, bonus
FROM employee
WHERE dept_id = 2
ORDER by bonus;
不同數(shù)據(jù)庫系統(tǒng)對于空值的排序位置采用了不同的處理方式。MySQL、Microsoft SQL Server 以及 SQLite 中的空值排在了最前,查詢返回的結(jié)果如下:
-- MySQL、Microsoft SQL Server 以及 SQLite
emp_name|bonus
--------|-------
黃忠 |
魏延 |
諸葛亮 |8000.00
Oracle 和 PostgreSQL 中的空值排在了最后,查詢返回的結(jié)果如下:
-- Oracle 以及 PostgreSQL
emp_name|bonus
--------|-------
諸葛亮 |8000.00
黃忠 |
魏延 |
另外,Oracle、PostgreSQL 以及 SQLite 支持使用 NULLS FIRST 關(guān)鍵字,將空值排在最前;或者使用 NULLS LAST 關(guān)鍵字,將空值排在最后。以下查詢語句返回的結(jié)果與上面的 MySQL 和 Microsoft SQL Server 一致:
-- 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 指定空值的排序位置。
中文的排序方式
在創(chuàng)建數(shù)據(jù)庫或者表時,我們通常會指定一個字符集和排序規(guī)則。字符集(Charset)決定了數(shù)據(jù)庫能夠存儲哪些字符,比如 ASCII 字符集只能存儲簡單的英文、數(shù)字和一些控制字符,GB2312 字符集可以存儲中文,Unicode 字符集能夠支持世界上的各種文字。
排序規(guī)則(Collation)定義了字符集中字符的排序順序,包括是否區(qū)分大小寫、是否區(qū)分重音等。對于中文而言,排序方式與英文有所不同,中文通常需要按照拼音、偏旁部首或者筆畫進行排序。
如果想要支持中文排序,最簡單的方式就是使用支持中文排序的字符集和排序規(guī)則。如果使用的字符集和排序規(guī)則不滿足我們的排序需求,可以通過其他方法實現(xiàn)。
Oracle 默認使用 AL32UTF8 字符編碼,中文按照偏旁部首進行排序。我們可以通過一個轉(zhuǎn)換函數(shù)實現(xiàn)其他方式的中文排序,以下查詢按照員工姓名的拼音進行排序:
-- Oracle 實現(xiàn)中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY NLSSORT(emp_name,'NLS_SORT = SCHINESE_PINYIN_M');
其中,NLSSORT()是 Oracle 提供的一個系統(tǒng)函數(shù),用于返回按照指定排序規(guī)則編碼的字符序列,SCHINESE_PINYIN_M 表示中文的拼音排序規(guī)則。查詢返回的結(jié)果如下:
EMP_NAME
--------
關(guān)平
關(guān)興
廖化
馬岱
張苞
趙氏
趙統(tǒng)
趙云
周倉
除按照拼音排序外,Oracle 還支持按照偏旁部首(SCHINESE_RADICAL_M)以及筆畫(SCHINESE_STROKE_M)進行中文排序。
MySQL 8.0 默認使用 utf8mb4 字符編碼,中文按照偏旁部首進行排序。我們可以通過一個轉(zhuǎn)換函數(shù)實現(xiàn)其他方式的中文排序,以下查詢按照員工姓名的拼音進行排序:
-- MySQL 實現(xiàn)中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY CONVERT(emp_name USING GBK);
其中,CONVERT()是一個 MySQL 系統(tǒng)函數(shù),用于轉(zhuǎn)換數(shù)據(jù)的字符集編碼,中文 GBK 字符集默認使用拼音進行排序。查詢返回的結(jié)果和上面的 Oracle 示例相同。
Microsoft SQL Server 中的字符集和排序規(guī)則是同一個概念,安裝數(shù)據(jù)庫時默認根據(jù)操作系統(tǒng)所在的區(qū)域進行設置,中國地區(qū)默認使用 Chinese_PRC_CI_AS 排序規(guī)則,中文按照偏旁部首進行排序。我們可以通過 COLLATE 關(guān)鍵字實現(xiàn)其他方式的中文排序,以下查詢按照員工姓名的拼音進行排序:
-- SQL Server 實現(xiàn)中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY emp_name COLLATE Chinese_PRC_CI_AI_KS_WS;
其中,COLLATE 表示按照某種排序規(guī)則進行排序,Chinese_PRC_CI_AI_KS_WS 表示中文拼音排序規(guī)則。查詢返回的結(jié)果和上面的 Oracle 示例一樣。
Microsoft SQL Server 也支持中文按照筆畫進行排序(Chinese_PRC_Stroke_CI_AS)。
PostgreSQL 默認使用 UTF-8 編碼字符集,中文按照偏旁部首進行排序。我們可以通過 COLLATE 關(guān)鍵字實現(xiàn)其他方式的中文排序,以下查詢按照員工姓名的拼音進行排序:
-- PostgreSQL 實現(xiàn)中文拼音排序
SELECT emp_name
FROM employee
WHERE dept_id = 4
ORDER BY emp_name COLLATE "zh_CN";
其中,COLLATE 表示按照某種排序規(guī)則進行排序,zh_CN 表示中文拼音排序規(guī)則。查詢返回的結(jié)果和上面的 Oracle 示例一樣。
SQLite 默認使用 UTF-8 字符編碼,中文按照偏旁部首進行排序,不支持其他的排序方式。