23.2. 排序规则支持

23.2.1. 概念
23.2.2. 管理排序规则

排序规则特性允许指定每一列甚至每一个操作的数据的排序顺序和字符分类行为。这放松了数据库的LC_COLLATELC_CTYPE设置自创建以后就不能更改这一限制。

23.2.1. 概念

在概念上,一种可排序数据类型的每一种表达式都有一个排序规则(内建的可排序数据类型是textvarcharchar。用户定义的基础类型也可以被标记为可排序的,并且在一种可排序数据类型上的域也是可排序的)。如果该表达式是一个列引用,该表达式的排序规则就是列所定义的排序规则。如果该表达式是一个常量,排序规则就是该常量数据类型的默认排序规则。更复杂表达式的排序规则根据其输入的排序规则得来,如下所述:

一个表达式的排序规则可以是默认排序规则,它表示数据库的区域设置。一个表达式的排序规则也可能是不确定的。在这种情况下,排序操作和其他需要知道排序规则的操作会失败。

当数据库系统必须要执行一次排序或者字符分类时,它使用输入表达式的排序规则。这会在使用例如ORDER BY子句以及函数或操作符调用(如<)时发生。应用于ORDER BY子句的排序规则就是排序键的排序规则。应用于函数或操作符调用的排序规则从它们的参数得来,具体如下文所述。除比较操作符之外,在大小写字母之间转换的函数会考虑排序规则,例如lowerupperinitcap。模式匹配操作符和to_char及相关函数也会考虑排序规则。

对于一个函数或操作符调用,其排序规则通过检查在执行指定操作时参数的排序规则来获得。如果该函数或操作符调用的结果是一种可排序的数据类型,万一有外围表达式要求函数或操作符表达式的排序规则,在解析时结果的排序规则也会被用作函数或操作符表达式的排序规则。

一个表达式的排序规则派生可以是显式或隐式。该区别会影响多个不同的排序规则出现在同一个表达式中时如何组合它们。当使用一个COLLATE子句时,将发生显式排序规则派生。所有其他排序规则派生都是隐式的。当多个排序规则需要被组合时(例如在一个函数调用中),将使用下面的规则:

  1. 如果任何一个输入表达式具有一个显式排序规则派生,则在输入表达式之间的所有显式派生的排序规则必须相同,否则将产生一个错误。如果任何一个显式派生的排序规则存在,它就是排序规则组合的结果。

  2. 否则,所有输入表达式必须具有相同的隐式排序规则派生或默认排序规则。如果任何一个非默认排序规则存在,它就是排序规则组合的结果。否则,结果是默认排序规则。

  3. 如果在输入表达式之间存在冲突的非默认隐式排序规则,则组合被认为是具有不确定排序规则。这并非一种错误情况,除非被调用的特定函数要求提供排序规则的知识。如果它确实这样做,运行时将发生一个错误。

例如,考虑这个表定义:

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名称映射到操作系统中安装的库提供的locale。一种排序规则定义有一个提供者,它指明哪个库提供locale数据。一种标准的提供者名称是libc,它使用由操作系统C库提供的locale。这些是操作系统所提供的大部分工具所使用的locale。另一种提供者是icu,它使用外部的ICU库。只有在PostgreSQL编译时配置了对ICU的支持时,才能使用ICU的locale。

libc提供的排序规则对象映射到LC_COLLATELC_CTYPE设置的一种组合,它能被setlocale()系统库调用所接受(正如其名称所表示的,排序规则的主要目的是设置LC_COLLATE,它控制排序顺序。但是实际上很少需要让LC_CTYPE的设置与LC_COLLATE不同,因此把这些集合在一种概念之下比为每个表达式的LC_CTYPE设置创建另一种机制要更加方便)。此外,libc排序规则与字符集编码(见Section 23.3)关联在一起。不同的编码可能存在相同的排序规则名称。

icu提供的排序规则对象映射到一种由ICU库提供的指定排序器。ICU不支持分离的collate以及ctype设置,因此它们总是相同的。此外,ICU排序规则是独立于编码的,因此在一个数据库中对于一个给定的名称总是只有一个ICU排序规则。

23.2.2.1. 标准排序规则

在所有的平台上,名为defaultC以及POSIX的排序规则都可用。根据操作系统的支持,可能还有额外的排序规则可用。default排序规则选择在数据库创建时的LC_COLLATE值和LC_CTYPE值。CPOSIX排序规则都指定传统的C行为,在其中只有ASCII字母AZ被当做字母,并且排序会严格地根据字符代码的字节值来进行。

此外,SQL的标准排序规则名ucs_basic对于编码UTF8来说是可用的。它等效于C,并且根据Unicode代码点来排序。

23.2.2.2. 预定义排序规则

如果操作系统提供对于在单一程序中使用多种locale的支持(newlocale以及相关函数),或者是如果配置了对ICU的支持,那么当数据库集簇被初始化时,initdb会用基于它当时在操作系统中找到的所有locale的排序规则来填充系统目录pg_collation

要查看当前可用的locale,可以使用查询SELECT * FROM pg_collation或者psql中的命令\dOS+

23.2.2.2.1. libc排序规则

例如,操作系统可能会提供一种名为de_DE.utf8的locale。那么initdb将为编码UTF8创建一个名为de_DE.utf8的排序规则,它的LC_COLLATE以及LC_CTYPE都被设置为de_DE.utf8。它还将用去掉.utf8标记的名字创建一个排序规则。因此用户还可以使用de_DE这个排序规则,它写起来没那么麻烦并且名称与编码无关。不管怎样,要注意初始的排序规则名称集合是平台相关的。

libc所提供的默认排序规则集合直接映射到操作系统装已安装的locale,它们可以用命令locale -a列出。如果需要一个libc排序规则的LC_COLLATELC_CTYPE具有不同值,或者在数据库系统被初始化之后在操作系统上安装了新的locale,则可以使用CREATE COLLATION命令创建一个新的排序规则。也可以用pg_import_system_collations()函数导入新的操作系统locale。

在任何特定的数据库中,只有使用数据库编码的排序规则是令人感兴趣的。其他pg_collation中的项会被忽略。因此,一个如de_DE的被剥离的排序规则名在一个给定数据库中可以被认为是唯一的,即使它在全局上并不唯一。我们推荐使用被剥离的排序规则名,因为在你决定要更改到另一个数据库编码时需要做的事情更少。但是要注意defaultCPOSIX排序规则在使用时可以不考虑数据库编码。

PostgreSQL在碰到具有相同属性的不同排序规则对象时会认为它们是不兼容的。因此对于例子:

SELECT a COLLATE "C" < b COLLATE "POSIX" FROM test1;

将会得到一个错误,即使CPOSIX排序规则具有相同的行为。因此,我们不推荐混合使用被剥离的和非被剥离的排序规则名。

23.2.2.2.2. ICU排序规则

使用ICU时,枚举所有可能的locale名称是不明智的。ICU对locale采用了一种特殊的命名系统,但是对一个locale命名的方式比实际有的locale要多得多。initdb使用ICU的API抽取一个可区分的locale集合来填充初始的排序规则集合。ICU提供的排序规则在SQL环境中的名称是BCP 47语言标签格式,后面追加一个专用的后缀-x-icu,通过这种方式把它们与libc的locale区分开。

这里有一些可能会被创建的排序规则的例子:

de-x-icu

德语排序规则,默认变体

de-AT-x-icu

奥地利的德语排序规则,默认变体

(也就是说,还有de-DE-x-icu或者de-CH-x-icu,但在编写这份文档之时,它们与de-x-icu等效。)

und-x-icu (for undefined)

ICU的root排序规则。使用它可以得到一种合理的与语言无关的排序顺序。

有些(较少使用的)编码不受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排序规则已经包括了数据库实例被初始化时操作系统中定义的所有排序规则,所以通常没有必要手工创建新的排序规则。手工创建排序规则的原因可能是想要一种不同的命名系统(这种情况还可以参考Section 23.2.2.3.3)或者是操作系统升级后提供了新的locale定义(这种情况还可以参考pg_import_system_collations())。

23.2.2.3.2. ICU排序规则

ICU允许把排序规则自定义成initdb预装载的基本语言+国家集合之外的形式。我们鼓励用户定义他们自己的排序规则对象来利用这些设施满足他们所要求的排序行为。有关ICU的locale命名信息请参考http://userguide.icu-project.org/localehttp://userguide.icu-project.org/collation/api。可接受的名称及属性集合取决于特定的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 locale。第二个例子使用传统的ICU相关的locale语法。第一种风格对于未来更好,但是旧的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');

带有Emoji排序规则类型的根排序规则,按照Unicode技术标准#51

观察一下在传统的ICU locale命名系统中,根locale是如何被一个空字符串选中的。

CREATE COLLATION digitslast (provider = icu, locale = 'en-u-kr-latn-digit');
CREATE COLLATION digitslast (provider = icu, locale = 'en@colReorder=latn-digit');

把数字排在拉丁字母之后(默认是数字在字母前面)。

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-latn-digit');
CREATE COLLATION special (provider = icu, locale = 'en@colCaseFirst=upper;colReorder=latn-digit');

组合上面的选项。

CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');
CREATE COLLATION numeric (provider = icu, locale = 'en@colNumeric=yes');

数字顺序,把数字序列按它们的数字值排序,例如:A-21 < A-123(也就是自然序)。

详情请参考Unicode Technical Standard #35BCP 47。可能的排序规则类型的列表(co子标签)可以在CLDR repository中找到。ICU Locale Explorer可以被用来检查一种特定locale定义的细节。使用k*子标签的例子需要ICU的版本至少为版本54。

注意虽然这个系统允许创建忽略大小写或者忽略重音或者类似形式(使用ks键)的排序规则,但PostgreSQL当前还不允许这类排序规则真地以大小写或者重音无关的方式运行。任何根据该排序规则比较后相等但在字节上不等的字符串将根据其字节值来排序。

Note

根据设计,ICU将接受几乎任何字符串作为locale名称,并且使用其文档中描述的fallback过程将它匹配到所能提供的最接近的locale。因此,如果一种排序规则说明使用了给定的ICU安装实际没有提供的特性,那么将不会有直接的反馈。所以,我们推荐创建应用级别的测试来检查排序规则定义是否满足用户的需求。

23.2.2.3.3. 拷贝排序规则

命令CREATE COLLATION也可以用来从现有的排序规则创建新的排序规则,它对于在应用中使用操作系统无关的排序规则名、创建兼容性名称或者用可读性更好的名称使用ICU提供的排序规则很有用。例如:

CREATE COLLATION german FROM "de_DE";
CREATE COLLATION french FROM "fr-x-icu";