PostgreSQL 中的重音区分排序

Accent sensitive sort in PostgreSQL

提问人:va. 提问时间:10/13/2023 更新时间:10/20/2023 访问量:142

问:

如何按拉脱维亚语字母顺序对PostgreSQL中的文本进行排序,因此任何没有重音的字母总是在有重音的字母之前?

例如,这个

select * from (
    select 'bā' as name
    union select 'āa'
    union select 'ac'
    union select 'Āb'
    union select 'Ā'
) t
order by name COLLATE "lv-x-icu"

返回Ā āa Āb ac bā

但预期的结果是ac Ā āa Āb bā

我尝试创建一些自定义排序规则,但它们都没有返回预期的结果。

PostgreSQL :SQL-ORDER-BY 排序规则、 字母排序 、区分重音

评论


答:

5赞 Laurenz Albe 10/13/2023 #1

这是一个不寻常的要求。在自然语言排序规则(包括拉图维亚排序规则)中,字符串按不同的步骤进行比较:

  • 在第一步中,标点符号、大小写和重音符号将被忽略。如果字符串比较不同,则决定了排序顺序。这称为初级差异

  • 如果字符串在主要级别上相等,则仅忽略标点符号和大小写,但重音很重要。如果字符串比较不同,则决定了排序顺序。这是中学阶段的区别。

  • 如果字符串在二级上相等,则仅忽略标点符号,但重音和大小写很重要。这是高等教育。

  • 最后,如果字符串在三级水平上相等,则标点符号(四级)的差异很重要。

这解释了你看到的排序顺序:因为在初级阶段,口音没有被考虑在内。a < aa < ab < ac <ba

如果你愿意,你希望重音在初级上被认为是不同的,就好像 和 是不同的字母一样。为此,您必须使用 ICU 排序规则并使用其他规则对其进行自定义。这只能从 v16 开始实现:ac < Āaā

CREATE COLLATION latuvian_custom (
   PROVIDER = icu,
   LOCALE = 'lv',
   RULES = '& a < ā & A < Ā & c < č & C < Č & e < ē & E < Ē & g < ģ & G < Ģ & k < ķ & K < Ķ & l < ļ & L < Ļ & n < ņ & N < Ņ & s < š & S < Š & u < ū & U < Ū & z < ž & Z < Ž'
);

select * from (
    select 'bā' as name
    union select 'āa'
    union select 'ac'
    union select 'Āb'
    union select 'Ā'
) t
order by name COLLATE latuvian_custom;

 name 
══════
 ac
 Ā
 Āb
 āa
 bā
(5 rows)

这些规则在无重音字符和重音字符之间引入了主要级别差异 ()。<

评论

0赞 The Impaler 10/13/2023
我向你致敬,劳伦斯。很好的答案。
0赞 va. 10/16/2023
这适用于 PostgreSQL v16,谢谢!让我们看看是否有任何针对旧 pg 版本的解决方案,因为我们使用的是 PostgreSQL v14,并且在不久的将来没有升级计划。
0赞 Laurenz Albe 10/17/2023
我重新设计了答案,以解释正在发生的事情,并删除了对大小写差异的关注。我很确定您无法在 v16 以下的 PostgreSQL 版本中创建这样的排序规则。
1赞 Zegarek 10/20/2023 #2

对@Laurenz的伟大答案的一个小补充:为了达到问题中指定的预期结果,需要在初级阶段考虑口音,但大小写不应该。演示ac Ā āa Āb bā

CREATE COLLATION latvian_custom (PROVIDER=icu,LOCALE='lv',
   RULES='&a<Ā=ā &c<Č=č &e<Ē=ē &g<Ģ=ģ &i<Ī=ī &k<Ķ=ķ 
          &l<Ļ=ļ &n<Ņ=ņ &s<Š=š &u<Ū=ū &z<Ž=ž'
);
SELECT string_agg(sample, E'\t' ORDER BY sample COLLATE "latvian_custom") 
FROM (VALUES ('bā'),('āa'),('ac'),('Āb'),('Ā')) AS _(sample);
string_agg
ac Ā āa Āb bā

在那个阶段,所有非重音字符都已经等于它们的大写变体,因此将相等的重音对锚定在任一字符后面就足够了(例如 或 )。每个重音字符的两个大小写变体都需要定位为相等 - 作为加法,它们是独立处理的,所以如果你只这样做,它就不起作用了。&A<Ā=ā&a<Ā=ā&a<ā &c<č...&z<ž

下面是一个比较:

collation_name sorted_samples
预期 ac Ā āa Āb bā (OP的例子)
违约 ac bā Ā Āb āa
latUvian_custom ac Ā Āb āa bā
latvian_custom ac Ā āa Āb bā (唯一比赛)
latvian_lower_first Ā āa Āb ac bā
latvian_lower_first_custom ac Ā Āb āa bā
latvian_upper_first Ā āa Āb ac bā
latvian_upper_first_custom ac Ā Āb āa bā
lv-LV-x-icu Ā āa Āb ac bā
LV-X-ICU Ā āa Āb ac bā
Unicode的 Ā āa Āb ac bā

评论

0赞 va. 10/22/2023
感谢您的研究和详细回答!