按排序规则排序

8

拥有一个集合:

{"name": "a"},
{"name": "B"},    
{"name": "b"},    
{"name": "c"},    
{"name": "á"},    
{"name": "A"}

例如如何对西班牙语进行不区分大小写的排序?

我尝试了这个:

var abc = [{"name": "a"}, {"name": "B"}, {"name": "b"}, {"name": "c"}, {"name": "á"}, {"name": "A"}];
for (i in abc) db.abc.save(abc[i]);

db.abc.find({},{"_id":0}).sort({"name":1});

输出结果是:

[
    { "name" : "A" },
    { "name" : "B" },
    { "name" : "a" },
    { "name" : "b" },
    { "name" : "c" },
    { "name" : "á" },
]

期望结果:

[
    { "name" : "a" },
    { "name" : "á" },
    { "name" : "A" },
    { "name" : "b" },
    { "name" : "B" },
    { "name" : "c" }
]
5个回答

11

我知道这是一个旧的线程,但我认为回答仍然有用。

你绝对不想在应用程序中进行排序,因为这意味着你必须将集合中的所有文档都加载到内存中进行排序并返回所需的窗口。如果你的集合非常庞大,那么这种方法极其低效。数据库应该进行排序并将窗口返回给你。

但是,你说MongoDB不支持本地敏感排序。你如何解决这个问题?秘密在于“排序键”的概念。

基本上,假设你有从“a”到“z”的常规英语/拉丁字母表。你需要创建一个从“a”到“01”,从“b”到“02”,依此类推到“z”到“26”的排序键映射。也就是说,将每个字母映射到该语言的排序顺序中的数字,并将该数字编码为字符串。然后,将你要排序的字符串映射到这种类型的排序键。例如,“abc”将变成“010203”。然后为属性添加一个与区域设置名称一起使用的排序键属性,以及属性的名称:

{
    name: "abc",
    name_en: "010203"
}

现在,您可以通过在“name_en”属性上进行索引,并对选择器和范围使用传统的基于英语的MongoDB排序,而不是“name”属性,以便在“en”语言中进行排序。

现在,假设您有另一种疯狂的语言“xx”,其中字母表的顺序为“acb”而不是“abc”。(是的,有些语言会以这种方式混淆拉丁字母的顺序!)此时,排序键将如下所示:

{
    name: "abc",
    name_en: "010203",
    name_xx: "010302"
}

现在,你需要在name_en和name_xx上创建索引,并使用常规的MongoDB排序以正确地按这些语言环境进行排序。基本上,这些额外属性是不同语言环境下排序的代理。

那么,你会问从哪里获取这些映射呢?毕竟,你不是全球化专家,对吧?

好吧,如果你正在使用Java、C或C++,那么有现成的类可以为你完成这个映射。在Java中,使用标准的Collator类,或者使用icu4j Collator类。如果你使用C/C++,请使用ICU Collator函数/类的C/C++版本。对于其他语言,除非你能找到已经实现了这个映射的库,否则你可能要自己动手了。

以下是一些链接,可以帮助你找到它们:

标准的Java库Collator:http://docs.oracle.com/javase/7/docs/api/java/text/Collator.html#getCollationKey(java.lang.String)

C++的Collator类:http://icu-project.org/apiref/icu4c/classicu_1_1Collator.html#ae0bc68d37c4a88d1cb731adaa5a85e95

你还可以创建不同的排序键,允许你在每个语言环境下进行不区分大小写、不区分重音和Unicode变体的排序,或任何以上的组合。唯一的问题是现在你有许多与每个可排序属性相平行的属性,并且在更新基本的“name”属性时必须将它们全部保持同步。这很麻烦,但仍然比在应用程序或业务逻辑层中进行排序要好。

另外要小心范围内的游标。例如,在英语中,我们只忽略字符上的重音符号。因此,“Ö”的排序方式与“O”相同,它将出现在范围“M”到“Z”内。但在瑞典语中,有重音符号的字符排在“Z”之后。因此,如果你做一个范围为“M”-“Z”,你将包括许多以“Ö”开头的记录,这些记录应该存在于英语中,但不应该存在于瑞典语中。

这也会影响到如果你在文档的文本属性上进行切割的话,会带来分片的影响。请注意哪些范围进入哪个分片。最好在不受语言环境影响的东西上进行切割,比如哈希。


那么,根据这个例子,你会在第5或第6个字符停止,并在较短的单词上填充0吗? - Stephane
不,较短的字符串总是“胜出”比较。尽管它们具有相同的前缀,但“abc”在“abcdef”之前排序,因此“010203”应该在“010203040506”之前排序。应将排序键属性视为字符串而不是数字进行比较。 - Edwin Hoogerbeets
哦,Java和C++排序器返回的排序键看起来与我在这里给出的示例非常不同。我只是使用“01”、“02”等等,因为它们很容易理解。例如,在Java中,getCollationKey()方法返回一个包含位打包排序元素的整数数组。我建议将此数组转换为十六进制数字字符串,以便MongoDB可以将它们作为字符串与默认的英语比较规则进行比较,这在十六进制上运行良好。 - Edwin Hoogerbeets

11

尽管其他答案适用于MongoDB版本3.2.x及以前,但从3.4.0开始,您可以“为集合或视图、索引或支持排序规则的特定操作指定排序规则”。

此功能的完整文档在此处


现在这将是正确的答案。因为MongoDB允许在创建集合时定义排序规则,或者使用所需的排序规则创建视图。请参阅:https://docs.mongodb.com/manual/reference/method/db.createCollection/#createcollection-collation-example - Marcos Cassiano
我一定会放弃 MongoDB,因为我认为它没有与我的母语——巴西葡萄牙语——轻松使用的方法。但这似乎是一个非常好的解决方案。 - Marcos Cassiano

3

目前,MongoDB没有实现校对功能。

实现Unicode校对标准是解决这个问题的最佳方式。

但这会使排序变慢,索引变大。因此,现在最好在应用程序中进行排序。


2
一个简单的解决方法是创建一个新字段,将文本转换为纯ascii字符。
{ "name": "Ánfora", "name_sort": "anfora" }
{ "name": "Óscar", "name_sort": "oscar" }
{ "name": "Barça", "name_sort": "barc~a" }
{ "name": "Niño", "name_sort": "nin~o" }
{ "name": "¡Hola!", "name_sort": "hola!" }
{ "name": "¿qué?", "name_sort": "que?" }

然后只需按“name_sort”排序即可


类似这样的方法是可行的。像其他人建议的那样,在应用程序中进行排序并不是一个可行的替代方案,当你有数百万行数据时,你必须创建一个可排序的字段来实现真正的解决方法。 - Csongor Fagyal

1

很抱歉,目前还无法进行不区分大小写的排序,现在排序是按“索引”顺序返回的。有一个未解决的问题:

https://jira.mongodb.org/browse/SERVER-90

您可以考虑在应用程序中跳过Mongo的排序,然后在应用程序中进行排序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接