23.2. 排序规则支持 #
排序规则特性允许为每一列甚至每一次操作指定数据的排序顺序和字符分类行为。这缓解了数据库的 LC_COLLATE 和 LC_CTYPE
设置在创建之后无法更改这一限制。
23.2.1. 概念
从概念上讲,每个支持排序规则的数据类型的表达式都有一个排序规则。(内置的支持排序规则的数据类型包括 text、varchar 和 char。用户定义的基础类型也可以标记为支持排序规则,当然,建立在支持排序规则的数据类型之上的域也支持排序规则。)如果表达式是列引用,则该表达式的排序规则就是该列定义的排序规则。如果表达式是常量,则其排序规则就是该常量数据类型的默认排序规则。更复杂表达式的排序规则则按下文所述,从其输入表达式的排序规则推导出来。
表达式的排序规则可以是“默认”排序规则,这表示为数据库定义的区域设置。表达式的排序规则也可能是不确定的。在这种情况下,排序操作以及其他需要知道排序规则的操作都会失败。
当数据库系统必须执行排序或字符分类时,它会使用输入表达式的排序规则。这例如发生在 ORDER BY 子句以及函数或操作符调用(如
<)中。应用于 ORDER BY 子句的排序规则就是排序键的排序规则。应用于函数或操作符调用的排序规则则按下文所述,从参数中推导出来。除了比较操作符之外,在大小写之间进行转换的函数(例如 lower、upper 和
initcap)、模式匹配操作符,以及 to_char
及相关函数,也都会考虑排序规则。
对于函数或操作符调用,通过检查参数排序规则推导出的排序规则,会在运行时用于执行指定操作。如果该函数或操作符调用的结果属于支持排序规则的数据类型,那么在解析时它也会被用作该函数或操作符表达式的已定义排序规则,以便在外围表达式需要知道其排序规则时使用。
表达式的排序规则派生可以是显式的,也可以是隐式的。这一区别会影响当一个表达式中出现多个不同排序规则时,系统如何把它们组合起来。使用 COLLATE 子句时,会发生显式排序规则派生;其他所有排序规则派生都是隐式的。当需要组合多个排序规则时,例如在函数调用中,将使用以下规则:
如果任一输入表达式具有显式排序规则派生,那么输入表达式中所有显式派生的排序规则都必须相同,否则会报错。如果存在显式派生的排序规则,那么排序规则组合的结果就是该排序规则。
否则,所有输入表达式都必须具有相同的隐式排序规则派生,或者为默认排序规则。如果存在任何非默认排序规则,那么排序规则组合的结果就是该排序规则;否则,结果就是默认排序规则。
如果输入表达式之间存在相互冲突的非默认隐式排序规则,则该组合会被视为具有不确定排序规则。除非被调用的特定函数确实需要知道它应当使用哪个排序规则,否则这并不是错误。如果它确实需要,就会在运行时抛出错误。
例如,考虑如下表定义:
CREATE TABLE test1 (
a text COLLATE "de_DE",
b text COLLATE "es_ES",
...
);
那么在
SELECT a < 'foo' FROM test1;
中,<比较会按 de_DE 规则进行,因为该表达式组合了一个隐式派生的排序规则和默认排序规则。但在
SELECT a < ('foo' COLLATE "fr_FR") FROM test1;
中,比较会按 fr_FR 规则进行,因为显式排序规则派生覆盖了隐式派生。进一步,给定
SELECT a < b FROM test1;
解析器无法确定应当应用哪个排序规则,因为 a 列和 b 列具有冲突的隐式排序规则。由于
<操作符确实需要知道要使用哪个排序规则,因此这会导致一个错误。该错误可以通过给任一输入表达式附加显式排序规则说明符来解决,例如:
SELECT a < b COLLATE "de_DE" FROM test1;
或者等价地:
SELECT a COLLATE "de_DE" < b FROM test1;
另一方面,结构相似的情况
SELECT a || b FROM test1;
不会导致错误,因为||操作符不关心排序规则:无论使用什么排序规则,其结果都相同。
如果函数或操作符返回的是支持排序规则的数据类型,那么分配给该函数或操作符组合输入表达式的排序规则,也被认为适用于其结果。因此,在
SELECT * FROM test1 ORDER BY a || 'foo';
中,排序将按 de_DE 规则进行。但这个查询:
SELECT * FROM test1 ORDER BY a || b;
会报错,因为即使||操作符本身不需要知道排序规则,ORDER BY 子句仍然需要。与之前一样,可以通过显式排序规则说明符来解决冲突:
SELECT * FROM test1 ORDER BY a || b COLLATE "fr_FR";
23.2.2. 管理排序规则 #
排序规则是一个 SQL 模式对象,它把某个 SQL 名称映射到操作系统中已安装库提供的区域设置。排序规则定义具有一个提供程序,用于指定由哪个库提供区域设置数据。标准提供程序之一是 libc,它使用操作系统 C 库提供的区域设置。这些也是大多数操作系统工具所使用的区域设置。另一个提供程序是 icu,它使用外部
ICU 库。只有在构建
PostgreSQL 时配置了 ICU 支持,才能使用 ICU
区域设置。
由 libc 提供的排序规则对象,对应于
LC_COLLATE 和 LC_CTYPE 设置的组合,其格式与系统库调用 setlocale() 所接受的格式相同。(顾名思义,排序规则的主要用途是设置控制排序顺序的
LC_COLLATE。但在实践中,很少有必要让
LC_CTYPE 和 LC_COLLATE 不同,因此把二者归为一个概念,比再建立一套为每个表达式设置 LC_CTYPE
的机制更方便。)此外,libc 排序规则还与某种字符集编码绑定(见第 23.3 节)。同一个排序规则名称可能会在不同编码中出现。
由 icu 提供的排序规则对象,对应于 ICU 库提供的具名排序器。ICU 不支持将“collate”和“ctype”分开设置,因此二者总是相同的。此外,ICU 排序规则与编码无关,因此在一个数据库中,某个给定名称的 ICU 排序规则始终只有一个。
23.2.2.1. 标准排序规则
所有平台都提供名为 default、C 和 POSIX 的排序规则。根据操作系统支持情况,还可能提供其他排序规则。default 排序规则选择创建数据库时指定的 LC_COLLATE 和 LC_CTYPE 值。C 和 POSIX 排序规则都采用“传统 C”行为,仅将 ASCII 字母“A”到“Z”视为字母,并严格按字符编码的字节值排序。
此外,编码 UTF8 还可以使用 SQL 标准排序规则名 ucs_basic。它等价于 C,按 Unicode 码点排序。
23.2.2.2. 预定义排序规则
如果操作系统支持在单个程序中使用多个区域设置(newlocale
及相关函数),或者配置了 ICU 支持,那么在初始化数据库集簇时,initdb 会根据当时在操作系统中发现的所有区域设置,用排序规则填充系统目录 pg_collation。
要检查当前可用的区域设置,可使用查询 SELECT * FROM pg_collation,或在 psql 中使用命令\dOS+。
23.2.2.2.1. libc 排序规则
例如,操作系统可能提供一个名为 de_DE.utf8 的区域设置。initdb 随后会为 UTF8 编码创建一个名为 de_DE.utf8 的排序规则,其
LC_COLLATE 和 LC_CTYPE 都设置为
de_DE.utf8。它还会再创建一个从名称中去掉
.utf8 标签的排序规则。因此,你也可以使用
de_DE 这个名称来使用该排序规则,这样写起来更方便,且名称与编码的耦合更小。不过请注意,初始排序规则名称集合仍然取决于平台。
由 libc 提供的默认排序规则,直接映射到操作系统中已安装的区域设置,它们可以用 locale -a 命令列出。如果需要某个 libc 排序规则,而其 LC_COLLATE
和 LC_CTYPE 取值不同,或者数据库系统初始化后又在操作系统中安装了新的区域设置,那么可以使用 CREATE COLLATION 命令创建新的排序规则。新的操作系统区域设置也可以用 pg_import_system_collations()
函数批量导入。
在任何特定数据库中,只有使用该数据库编码的排序规则才有意义。pg_collation 中的其他条目会被忽略。因此,像
de_DE 这样去掉编码后缀的排序规则名称,在某个给定数据库内可以视为唯一,即使它在全局范围内并不唯一。推荐使用这种去掉后缀的排序规则名称,因为如果你决定改用另一种数据库编码,需要改动的地方会更少。不过要注意,default、C 和 POSIX
排序规则不受数据库编码影响,始终都可以使用。
PostgreSQL 即使面对具有相同属性的不同排序规则对象,也会把它们视为不兼容。例如:
SELECT a COLLATE "C" < b COLLATE "POSIX" FROM test1;
即使 C 和 POSIX 排序规则的行为完全相同,这仍然会报错。因此,不建议混用去掉后缀和保留后缀的排序规则名称。
23.2.2.2.2. ICU 排序规则
对于 ICU,枚举所有可能的区域设置名称并不合理。ICU 为区域设置使用特定的命名系统,但给区域设置命名的方法远多于实际存在的不同区域设置。initdb 使用 ICU API 提取一组不同的区域设置,以填充初始排序规则集合。由 ICU 提供的排序规则在 SQL 环境中创建时,其名称采用 BCP 47 语言标签格式,并附加“私有使用”扩展
-x-icu,以便与 libc 区域设置区分开来。
下面是一些可能创建出来的排序规则示例:
de-x-icu德语排序规则,默认变体
de-AT-x-icu奥地利德语排序规则,默认变体
(还有例如
de-DE-x-icu或de-CH-x-icu,但在撰写本文时,它们与de-x-icu等价。)und-x-icu(表示“未定义”)ICU“根”排序规则。可用它获得一种较为合理、与特定语言无关的排序顺序。
某些(较少使用的)编码不受 ICU 支持。当数据库编码属于其中之一时,pg_collation 中的 ICU 排序规则条目会被忽略。尝试使用这样的排序规则会报出类似“collation "de-x-icu" for encoding
"WIN874" does not exist”的错误。
23.2.2.3. 创建新的排序规则对象 #
如果标准和预定义排序规则不足以满足需求,用户可以使用 SQL 命令 CREATE COLLATION 创建自己的排序规则对象。
与所有预定义对象一样,标准和预定义排序规则都位于模式
pg_catalog 中。用户定义的排序规则应当创建在用户模式中。这也能确保它们被 pg_dump 保存。
23.2.2.3.1. libc 排序规则
新的 libc 排序规则可以这样创建:
CREATE COLLATION german (provider = libc, locale = 'de_DE');
该命令中 locale 子句可接受的确切值取决于操作系统。在类 Unix 系统上,命令 locale -a 会给出一个列表。
由于预定义的 libc 排序规则已经包含了数据库实例初始化时操作系统中定义的所有排序规则,因此通常不需要手工创建新的排序规则。可能需要这样做的原因包括希望采用不同的命名系统(这种情况下另见第 23.2.2.3.3 节),或操作系统升级后提供了新的区域设置定义(这种情况下另见 pg_import_system_collations())。
23.2.2.3.2. ICU 排序规则
除了 initdb 预装载的基本语言与国家组合外,ICU 还允许进一步定制排序规则。建议用户定义自己的排序规则对象,利用这些功能使排序行为满足自己的需求。关于 ICU 区域设置名称的信息,参见 https://unicode-org.github.io/icu/userguide/locale/和 https://unicode-org.github.io/icu/userguide/collation/api.html。可接受的名称和属性集合取决于具体的 ICU 版本。
以下是一些示例:
CREATE COLLATION "de-u-co-phonebk-x-icu" (provider = icu, locale = 'de-u-co-phonebk');CREATE COLLATION "de-u-co-phonebk-x-icu" (provider = icu, locale = 'de@collation=phonebook');德语排序规则,电话簿排序类型
第一个示例使用 BCP 47 定义的“语言标签”来选择 ICU 区域设置。第二个示例使用传统的 ICU 专有区域设置语法。今后应优先采用第一种形式,但较旧的 ICU 版本不支持它。
注意,可以在 SQL 环境中为排序规则对象任意命名。本例遵循预定义排序规则所采用的命名风格,该风格也遵循 BCP 47,但用户定义的排序规则并不要求如此。
CREATE COLLATION "und-u-co-emoji-x-icu" (provider = icu, locale = 'und-u-co-emoji');CREATE COLLATION "und-u-co-emoji-x-icu" (provider = icu, locale = '@collation=emoji');根排序规则,表情符号排序类型,依据 Unicode 技术标准 #51
注意,传统的 ICU 区域设置命名系统使用空字符串来选择根区域设置。
CREATE COLLATION latinlast (provider = icu, locale = 'en-u-kr-grek-latn');CREATE COLLATION latinlast (provider = icu, locale = 'en@colReorder=grek-latn');将希腊字母排在拉丁字母之前。(默认情况下是拉丁字母排在希腊字母之前。)
CREATE COLLATION upperfirst (provider = icu, locale = 'en-u-kf-upper');CREATE COLLATION upperfirst (provider = icu, locale = 'en@colCaseFirst=upper');将大写字母排在小写字母之前。(默认情况下是小写字母在前。)
CREATE COLLATION special (provider = icu, locale = 'en-u-kf-upper-kr-grek-latn');CREATE COLLATION special (provider = icu, locale = 'en@colCaseFirst=upper;colReorder=grek-latn');结合了以上两个选项。
CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');数值排序,按数值对数字序列排序,例如:
A-21<A-123(也称自然排序)。
参见 Unicode 技术标准 #35 和 BCP 47 了解详情。可用排序类型列表(co 子标签)可在以下位置找到:CLDR 仓库。
注意,虽然此系统允许创建“忽略大小写”或“忽略重音”等排序规则(使用 ks 键),但要让这些排序规则真正不区分大小写或重音,还必须在 CREATE COLLATION 中将它们声明为非确定性排序规则;参见第 23.2.2.4 节。否则,按排序规则比较相等、但字节不相等的字符串,仍会按字节值排序。
注意
ICU 的设计允许接受几乎任何字符串作为区域设置名,并按照其文档描述的回退流程,匹配到它能够提供的最接近区域设置。因此,如果排序规则定义使用了当前 ICU 安装实际不支持的功能,不会得到直接反馈。所以建议创建应用层面的测试用例,以检查排序规则定义是否满足需求。
23.2.2.3.3. 复制排序规则 #
命令 CREATE COLLATION 也可以用来从现有排序规则创建一个新的排序规则。这有助于在应用程序中使用与操作系统无关的排序规则名称、创建兼容性名称,或者以更易读的名称来使用 ICU 提供的排序规则。例如:
CREATE COLLATION german FROM "de_DE"; CREATE COLLATION french FROM "fr-x-icu";
23.2.2.4. 非确定性排序规则 #
排序规则要么是确定性的,要么是非确定性的。确定性排序规则使用确定性比较,这意味着只有当两个字符串由完全相同的字节序列组成时,才会认为它们相等。非确定性比较则可能在两个字符串的字节不同的情况下,仍判断它们相等。典型情况包括不区分大小写的比较、不区分重音的比较,以及对采用不同 Unicode
规范化形式的字符串进行比较。具体是否实现这些不敏感比较,取决于排序规则提供程序;deterministic 标志只决定当比较结果相等时,是否再使用按字节比较来打破平局。更多术语信息可参见 Unicode 技术标准 10。
要创建非确定性排序规则,可在 CREATE COLLATION 中指定属性 deterministic = false,例如:
CREATE COLLATION ndcoll (provider = icu, locale = 'und', deterministic = false);
这个示例会以非确定性方式使用标准 Unicode 排序规则。特别地,它允许不同规范化形式的字符串被正确比较。更有意思的示例则会用到上文介绍的 ICU 定制功能。例如:
CREATE COLLATION case_insensitive (provider = icu, locale = 'und-u-ks-level2', deterministic = false); CREATE COLLATION ignore_accents (provider = icu, locale = 'und-u-ks-level1-kc-true', deterministic = false);
所有标准和预定义排序规则都是确定性的,用户定义的排序规则默认也都是确定性的。非确定性排序规则的行为更为“正确”,特别是考虑到 Unicode 的全部能力及其众多特殊情况时,但它们也有一些缺点。首先,使用它们会损失性能。此外,某些操作无法使用非确定性排序规则,例如模式匹配。因此,只应在明确需要时使用它们。