没有 ORDER BY,行的顺序就是不确定的
没有 ORDER BY 的 SELECT 会按 SQLite 觉得方便的顺序返回行。在小表上,它看起来常常像是插入顺序,于是人们就放心地依赖它。千万别这样。一旦用上了索引、表变大了,或者查询计划变了,这个顺序就可能毫无预兆地改变。
如果你在意行的顺序,就明确写出来:
ORDER BY name 默认按升序排序。结果是 Ada、Boris、Chen、Rosa,每次都按字母顺序返回,不管表在磁盘上是怎么存储的。
ASC 和 DESC
ASC 表示升序(从小到大、从 A 到 Z、从旧到新)。DESC 表示降序,也就是反过来。ASC 是默认值,所以你几乎总会看到它被省略:
这样最新注册的用户会排在最前面。用 ISO 8601 字符串(YYYY-MM-DD)存储的日期,按文本排序也是正确的。SQLite 没有专门的日期类型,这正是日期列推荐使用这种格式的原因之一。
按多列排序
当第一个排序列出现平局时,SQLite 会用第二列来打破平局。按优先级顺序用逗号分隔列出各列:
行先按国家分组(FR 在 US 前面),每个国家内部再按名字排序。每一列都可以有自己的方向:
国家按升序,每个国家内部按注册时间从新到旧。ASC 和 DESC 只作用于紧挨着它们的那一列,不会延续到其他列。
按表达式和别名排序
ORDER BY 接受任意表达式,不只是列名。这对计算出来的值很有用:
SELECT 列表里的别名 revenue 可以直接在 ORDER BY 中使用。你也可以把表达式再写一遍,比如 ORDER BY price * quantity DESC,效果完全一样。
你还可以按列的位置排序,不过最好别养成这个习惯:
SELECT name, price FROM products ORDER BY 2 DESC;
2 表示 select 列表中的第二列。这样写能用,但如果以后有人调整了列的顺序,排序的含义就会悄悄改变。请改用列名或别名排序。
NULL 排在哪里
NULL 表示“未知”,SQLite 必须决定未知值在排序中放在哪里。默认规则是:ASC 时 NULL 排在最前,DESC 时排在最后。
Ada 和 Chen 出现在最上面,排在所有真实日期之前。如果你想要的是“最新的排在最前”,这很少是你想要的结果。用 NULLS LAST 改变它:
现在真实日期排在前面,NULL 排在底部。NULLS FIRST 则相反。两者都是标准 SQL 的一部分,在 SQLite 3.30 及以后的版本中可用。
用 COLLATE NOCASE 实现不区分大小写的排序
SQLite 默认的文本比较是二进制的,按 Unicode 码点排序。这意味着大写字母排在小写字母前面,所以 'Zoe' 会排在 'apple' 前面:
结果是 Boris, Zoe, ada, apple:先是大写,再是小写。要不区分大小写地排序,就加上 NOCASE 排序规则:
现在得到的是 ada, apple, Boris, Zoe。NOCASE 只把 ASCII 的 A 到 Z 和 a 到 z 视为等同,不会处理重音符号或非 ASCII 字母。要实现真正国际化的排序,需要在应用层实现排序规则,不过对常见的英文场景来说,NOCASE 已经够用了。
随机排序
有时你会想让行按随机顺序排列,比如挑选每日推荐内容,或者抽样一些行来测试。SQLite 的 random() 函数返回一个随机整数,按它排序即可:
每一行都会得到一个新的随机值,排序就把它们打乱了。小表这样做没问题。在大表上,ORDER BY random() 很慢:它必须给每一行计算一个随机值,再对整个结果排序。要从巨大的表里抽取一行,更巧妙的做法(比如随机挑一个 rowid)会快得多。
常见陷阱
几个经常让人踩坑的地方:
- 忘了写
ORDER BY却假定有顺序。 没有它,顺序就是不确定的。即使看起来很稳定,实际上也并不稳定。 - 对存成文本的数字排序。 按字典序,
'10'会排在'2'前面。如果一列应该按数值排序,就用数值类型亲和性来存储(或者做类型转换:ORDER BY CAST(value AS INTEGER))。 - 在多列之间混用 ASC 和 DESC。 每一列都有自己的方向。
ORDER BY a, b DESC是a升序、b降序,而不是两列都降序。 - 为了取前几行而对巨大的结果集排序。 把
ORDER BY和LIMIT搭配使用,并给排序列建索引,这就是下一页的内容。
下一篇:LIMIT 与 OFFSET
排序告诉 SQLite 如何排列行;LIMIT 和 OFFSET 告诉它返回多少行、从哪里开始。它们一起构成了分页和“前 N 条”查询的核心,下一页就来讲。
常见问题
SQLite 中如何对结果排序?
在 SELECT 末尾加上 ORDER BY 子句,写出要排序的列。SELECT * FROM users ORDER BY name; 按升序排序。加上 DESC 就是降序:ORDER BY name DESC。没有 ORDER BY 时,行的顺序是没有保证的,即使看起来很稳定,也绝不要依赖它。
SQLite 中如何按多列排序?
用逗号分隔列出它们:ORDER BY country, name。SQLite 先按第一列排序,再用第二列打破平局。每一列都可以有自己的方向:ORDER BY country ASC, signup_date DESC。
SQLite 中如何做不区分大小写的排序?
在 ORDER BY 里使用 COLLATE NOCASE:ORDER BY name COLLATE NOCASE。SQLite 默认用二进制排序规则对文本排序,所以 Zoe 会排在 apple 前面。NOCASE 在排序时把大写和小写视为相同。
SQLite 排序结果中 NULL 会出现在哪里?
默认情况下,升序时 NULL 排在最前,降序时排在最后。你可以用 NULLS FIRST 或 NULLS LAST 改变这一点:ORDER BY signup_date DESC NULLS LAST 会让真实日期留在顶部,把缺失的日期推到底部。