归一化词元

把文本切割成词元(token)只是这项工作的一半。为了让这些词元(token)更容易搜索, 这些词元(token)需要被 归一化(normalization)--这个过程会去除同一个词元(token)的无意义差别,例如大写和小写的差别。可能我们还需要去掉有意义的差别, 让 estaéstaestá 都能用同一个词元(token)来搜索。你会用 déjà vu 来搜索,还是 deja vu?

这些都是语汇单元过滤器的工作。语汇单元过滤器接收来自分词器(tokenizer)的词元(token)流。还可以一起使用多个语汇单元过滤器,每一个都有自己特定的处理工作。每一个语汇单元过滤器都可以处理来自另一个语汇单元过滤器输出的单词流。

举个例子

用的最多的语汇单元过滤器(token filters)是 lowercase 过滤器,它的功能正和你期望的一样;它将每个词元(token)转换为小写形式:

GET /_analyze?tokenizer=standard&filters=lowercase
The QUICK Brown FOX! (1)
  1. 得到的词元(token)是 the, quick, brown, fox

只要查询和检索的分析过程是一样的,不管用户搜索 fox 还是 FOX 都能得到一样的搜索结果。lowercase 过滤器会将查询 FOX 的请求转换为查询 fox 的请求, fox 和我们在倒排索引中存储的是同一个词元(token)。

为了在分析过程中使用 token 过滤器,我们可以创建一个 custom 分析器:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_lowercaser": {
          "tokenizer": "standard",
          "filter":  [ "lowercase" ]
        }
      }
    }
  }
}

我们可以通过 analyze API 来验证:

GET /my_index/_analyze?analyzer=my_lowercaser
The QUICK Brown FOX! (1)
  1. 得到的词元是 the, quick, brown, fox

如果有口音

英语用变音符号(例如 ´, ^, 和 ¨) 来强调单词—​例如 rôle, déjà, 和 däis —​但是是否使用他们通常是可选的. 其他语言则通过变音符号来区分单词。当然,只是因为在你的索引中拼写正确的单词并不意味着用户将搜索正确的拼写。 去掉变音符号通常是有用的,让 rôle 对应 role, 或者反过来。 对于西方语言,可以用 asciifolding 字符过滤器来实现这个功能。 实际上,它不仅仅能去掉变音符号。它会把Unicode字符转化为ASCII来表示:

  • ßss

  • æae

  • łl

  • ɰm

  • ??

  • 2

  • 6

lowercase 过滤器一样, asciifolding 不需要任何配置,可以被 custom 分析器直接使用:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "folding": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "asciifolding" ]
        }
      }
    }
  }
}

GET /my_index?analyzer=folding
My œsophagus caused a débâcle (1)
  1. 得到的词元 my, oesophagus, caused, a, debacle

保留原意

理所当然的,去掉变音符号会丢失原意。 例如, 参考 这三个 西班牙单词:

esta

形容词 this 的阴性形式, 例如 esta silla (this chair) 和 esta (this one).

ésta

esta 的古代用法.

está

动词 estar (to be) 的第三人称形式, 例如 está feliz (he is happy).

通常我们会合并前两个形式的单词,而去区分和他们不相同的第三个形式的单词。类似的:

动词 saber (to know) 的第一人称形式 例如 Yo sé (I know).

se

与许多动词使用的第三人称反身代词, 例如 se sabe (it is known).

不幸的是,没有简单的方法,去区分哪些词应该保留变音符号和哪些词应该去掉变音符号。而且很有可能,你的用户也不知道.

相反, 我们对文本做两次索引: 一次用原文形式,一次用去掉变音符号的形式:

PUT /my_index/_mapping/my_type
{
  "properties": {
    "title": { (1)
      "type":           "string",
      "analyzer":       "standard",
      "fields": {
        "folded": { (2)
          "type":       "string",
          "analyzer":   "folding"
        }
      }
    }
  }
}
  1. title 字段用 standard 分析器,会保留原文的变音符号.

  2. title.folded 字段用 folding 分析器,会去掉变音符号

你可以使用 analyze API 分析 Esta está loca (This woman is crazy)这个句子,来验证字段映射:

GET /my_index/_analyze?field=title (1)
Esta está loca

GET /my_index/_analyze?field=title.folded (2)
Esta está loca
  1. 得到的词元 esta, está, loca

  2. 得到的词元 esta, esta, loca

可以用更多的文档来测试:

PUT /my_index/my_type/1
{ "title": "Esta loca!" }

PUT /my_index/my_type/2
{ "title": "Está loca!" }

现在,我们可以通过联合所有的字段来搜索。在`multi_match`查询中通过 most_fields mode 模式来联合所有字段的结果:

GET /my_index/_search
{
  "query": {
    "multi_match": {
      "type":     "most_fields",
      "query":    "esta loca",
      "fields": [ "title", "title.folded" ]
    }
  }
}

通过 validate-query API 来执行这个查询可以帮助你理解查询是如何执行的:

GET /my_index/_validate/query?explain
{
  "query": {
    "multi_match": {
      "type":     "most_fields",
      "query":    "está loca",
      "fields": [ "title", "title.folded" ]
    }
  }
}

multi-match 查询会搜索在 title 字段中原文形式的单词 (está),和在 title.folded 字段中去掉变音符号形式的单词 esta:

(title:está        title:loca       )
(title.folded:esta title.folded:loca)

无论用户搜索的是 esta 还是 está; 两个文档都会被匹配,因为去掉变音符号形式的单词在 title.folded 字段中。然而,只有原文形式的单词在 title 字段中。此额外匹配会把包含原文形式单词的文档排在结果列表前面。

我们用 title.folded 字段来 扩大我们的网 (widen the net)来匹配更多的文档,然后用原文形式的 title 字段来把关联度最高的文档排在最前面。在可以为了匹配数量牺牲文本原意的情况下,这个技术可以被用在任何分析器里。

Tip

asciifolding 过滤器有一个叫做 preserve_original 的选项可以让你这样来做索引,把词的原文词元(original token)和处理—​折叠后的词元(folded token)放在同一个字段的同一个位置。开启了这个选项,结果会像这样:

Position 1     Position 2
--------------------------
(ésta,esta)    loca
--------------------------

虽然这个是节约空间的好办法,但是也意味着没有办法再说“给我精确匹配的原文词元”(Give me an exact match on the original word)。包含去掉和不去掉变音符号的词元,会导致不可靠的相关性评分。

所以,正如我们这一章做的,把每个字段的不同形式分开到不同的字段会让索引更清晰。

Unicode的世界

当Elasticsearch在比较词元(token)的时候,它是进行字节(byte)级别的比较。 换句话说,如果两个词元(token)被判定为相同的话,他们必须是相同的字节(byte)组成的。然而,Unicode允许你用不同的字节来写相同的字符。

例如, é 的不同是什么?这取决于你问谁。对于Elasticsearch,第一个是由 0xC3 0xA9 这两个字节组成的,第二个是由 0x65 0xCC 0x81 这三个字节组成的。

对于Unicode,他们的差异和他们的怎么组成没有关系,所以他们是相同的。第一个是单个单词 é ,第二个是一个简单 e 和重音符 ´。

如果你的数据有多个来源,就会有可能发生这种状况:因为相同的单词使用了不同的编码,导致一个形式的 déjà 不能和它的其他形式进行匹配。

幸运的是,这里就有解决办法。这里有4种Unicode 归一化形式 (normalization forms) : nfc, nfd, nfkc, nfkd,它们都把Unicode字符转换成对应标准格式,把所有的字符 进行字节(byte)级别的比较。

Unicode归一化形式 (Normalization Forms)
_组合_ (_composed_) 模式—`nfc` 和 `nfkc`—用尽可能少的字节(byte)来代表字符。 ((("composed forms (Unicode normalization)"))) 所以用 `é` 来代表单个字母 `é` 。  _分解_ (_decomposed_) 模式—`nfd` and `nfkd`—用字符的每一部分来代表字符。所以 `é` 分解为 `e` 和 `´`。 ((("decomposed forms (Unicode normalization)")))

规范 (canonical) 模式—nfcnfd&—把连字作为单个字符,例如 或者 œ兼容 (compatibility) 模式—nfkcnfkd—将这些组合的字符分解成简单字符的等价物,例如: f + f + i 或者 o + e.

无论你选择哪一个归一化(normalization)模式,只要你的文本只用一种模式,那你的同一个词元(token)就会由相同的字节(byte)组成。例如,兼容 (compatibility) 模式 可以用连词 的简化形式 `ffi`来进行对比。

你可以使用 icu_normalizer 语汇单元过滤器(token filters) 来保证你的所有词元(token)是相同模式:

PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "nfkc_normalizer": { (1)
          "type": "icu_normalizer",
          "name": "nfkc"
        }
      },
      "analyzer": {
        "my_normalizer": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "nfkc_normalizer" ]
        }
      }
    }
  }
}
  1. nfkc 归一化(normalization)模式来归一化(Normalize)所有词元(token).

Tip

包括刚才提到过的 icu_normalizer 语汇单元过滤器(token filters)在内,这里还有 icu_normalizer 字符 过滤器(character filters)。虽然它和语汇单元过滤器做相同的工作,但是会在文本到达过滤器之前做。到底是用`standard` 过滤器,还是 icu_tokenizer 过滤器,其实并不重要。因为过滤器知道怎么来正确处理所有的模式。

但是,如果你使用不同的分词器,例如: ngram, edge_ngram, 或者 pattern 分词器,那么在语汇单元过滤器(token filters)之前使用 icu_normalizer 字符过滤器就变得有意义了。

通常来说,你不仅仅想要归一化(normalize)词元(token)的字节(byte)规则,还需要把他们转成小写字母。这个可以通过 icu_normalizer 和定制的归一化(normalization)的模式 nfkc_cf 来实现。下一节我们会具体讲这个。

Unicode 大小写折叠

人类没有创造力的话就不会是人类, 而人类的语言就恰恰反映了这一点。

处理一个单词的大小写看起来是一个简单的任务,除非遇到需要处理多语言的情况。

那就举一个例子:转换小写德国单词 ß。把它转换成大写是 SS,然后在转换成小写就成了 ss。还有一个例子:转换希腊字母 ς (sigma, 在单词末尾使用)。把它转换成大写是 Σ,然后再转换成小写就成了 σ

把词条小写的核心是让他们看起来更像,而不是更不像。在Unicode中,这个工作是大小写折叠(case folding)来完成的,而不是小写化(lowercasing)。 大小写折叠 (Case folding) 把单词转换到一种(通常是小写)形式,是让写法不会影响单词的比较,所以拼写不需要完全正确。

例如:单词 ß,已经是小写形式了,会被_折叠_(folded)成 ss。类似的小写的 ς 被折叠成 σ,这样的话,无论 σς, 和 `Σ`出现在哪里, 他们就都可以比较了。

`icu_normalizer` 语汇单元过滤器默认的归一化(normalization)模式是 `nfkc_cf`。它像 `nfkc` 模式一样:
  • 组合 (Composes) 字符用最短的字节来表示。

  • 兼容 (compatibility)模式,把像 的字符转换成简单的 ffi

但是,也会这样做:

  • 大小写折叠 (Case-folds) 字符成一种适合比较的形式

换句话说, nfkc_cf`等价于 `lowercase 语汇单元过滤器(token filters),但是却适用于所有的语言。 on-steroids 等价于 standard 分析器,例如:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_lowercaser": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "icu_normalizer" ] (1)
        }
      }
    }
  }
}
  1. icu_normalizer 默认是 nfkc_cf 模式.

我们来比较 Weißkopfseeadler`和 `WEISSKOPFSEEADLER(大写形式) 分别通过 `standard`分析器和我们的Unicode自识别(Unicode-aware)分析器处理得到的结果:

GET /_analyze?analyzer=standard (1)
Weißkopfseeadler WEISSKOPFSEEADLER

GET /my_index/_analyze?analyzer=my_lowercaser (2)
Weißkopfseeadler WEISSKOPFSEEADLER
  1. 得到的词元(token)是 weißkopfseeadler, weisskopfseeadler

  2. 得到的词元(token)是 weisskopfseeadler, weisskopfseeadler

    `standard`分析器得到了两个不同且不可比较的词元(token),而我们定制化的分析器得到了两个相同但是不符合原意的词元(token)。

Unicode 字符折叠

在多语言((("Unicode", "character folding")))((("tokens", "normalizing", "Unicode character folding")))处理中,`lowercase` 语汇单元过滤器(token filters)是一个很好的开始。但是作为对比的话,也只是对于整个巴别塔的惊鸿一瞥。所以 <<asciifolding-token-filter,`asciifolding` token filter>> 需要更有效的Unicode _字符折叠_ (_character-folding_)工具来处理全世界的各种语言。((("asciifolding token filter")))
`icu_folding` 语汇单元过滤器(token filters) (provided by the <<icu-plugin,`icu` plug-in>>)的功能和 `asciifolding` 过滤器一样, ((("icu_folding token filter")))但是它扩展到了非ASCII编码的语言,例如:希腊语,希伯来语,汉语。它把这些语言都转换对应拉丁文字,甚至包含它们的各种各样的计数符号,象形符号和标点符号。
`icu_folding` 语汇单元过滤器(token filters)自动使用 `nfkc_cf` 模式来进行大小写折叠和Unicode归一化(normalization),所以不需要使用 `icu_normalizer` :
PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_folder": {
          "tokenizer": "icu_tokenizer",
          "filter":  [ "icu_folding" ]
        }
      }
    }
  }
}

GET /my_index/_analyze?analyzer=my_folder
١٢٣٤٥ (1)
  1. 阿拉伯数字 ١٢٣٤٥ 被折叠成等价的拉丁数字: 12345.

如果你有指定的字符不想被折叠,你可以使用 UnicodeSet(像字符的正则表达式) 来指定哪些Unicode才可以被折叠。例如:瑞典单词 å,ä, ö, Å, Ä, 和 Ö 不能被折叠,你就可以设定为: [^åäöÅÄÖ] (^ 表示 不包含)。这样就会对于所有的Unicode字符生效。

PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "swedish_folding": { (1)
          "type": "icu_folding",
          "unicodeSetFilter": "[^åäöÅÄÖ]"
        }
      },
      "analyzer": {
        "swedish_analyzer": { (2)
          "tokenizer": "icu_tokenizer",
          "filter":  [ "swedish_folding", "lowercase" ]
        }
      }
    }
  }
}
  1. `swedish_folding`语汇单元过滤器(token filters) 定制了 `icu_folding`语汇单元过滤器(token filters)来不处理那些大写和小写的瑞典单词。

  2. swedish 分析器首先分词,然后用`swedish_folding`语汇单元过滤器来折叠单词,最后把他们走转换为小写,除了被排除在外的单词: Å, Ä, 或者 Ö

排序和整理

本章到目前为止,我们已经了解了怎么以搜索为目的去规范化词汇单元。 本章节中要考虑的最终用例是字符串排序。

字符串排序与多字段 (复数域)中,我们解释了 Elasticsearch 为什么不能在 analyzed (分析过)的字符串字段上排序,并演示了如何为同一个域创建 复数域索引 ,其中 analyzed 域用来搜索, not_analyzed 域用来排序。

analyzed 域无法排序并不是因为使用了分析器,而是因为分析器将字符串拆分成了很多词汇单元,就像一个 词汇袋 ,所以 Elasticsearch 不知道使用那一个词汇单元排序。

依赖于 not_analyzed 域来排序的话不是很灵活:这仅仅允许我们使用原始字符串这一确定的值排序。然而我们 可以 使用分析器来实现另外一种排序规则,只要你选择的分析器总是为每个字符串输出有且仅有一个的词汇单元。

大小写敏感排序

想象下我们有三个 用户 文档,文档的 姓名 域分别含有 BoffeyBROWNbailey 。首先我们将使用在 字符串排序与多字段 中提到的技术,使用 not_analyzed 域来排序:

PUT /my_index
{
  "mappings": {
    "user": {
      "properties": {
        "name": { (1)
          "type": "string",
          "fields": {
            "raw": { (2)
              "type":  "string",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
  }
}
  1. analyzed name 域用来搜索。

  2. not_analyzed name.raw 域用来排序。

我们可以索引一些文档用来测试排序:

PUT /my_index/user/1
{ "name": "Boffey" }

PUT /my_index/user/2
{ "name": "BROWN" }

PUT /my_index/user/3
{ "name": "bailey" }

GET /my_index/user/_search?sort=name.raw

运行这个搜索请求将会返回这样的文档排序: BROWNBoffeybailey 。 这个是 词典排序字符串排序 相反。基本上就是大写字母开头的字节要比小写字母开头的字节权重低,所以这些姓名是按照最低值优先排序。

这可能对计算机是合理的,但是对人来说并不是那么合理,人们更期望这些姓名按照字母顺序排序,忽略大小写。为了实现这个,我们需要把每个姓名按照我们想要的排序的顺序索引。

换句话来说,我们需要一个能输出单个小写词汇单元的分析器:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "case_insensitive_sort": {
          "tokenizer": "keyword",    (1)
          "filter":  [ "lowercase" ] (2)
        }
      }
    }
  }
}
  1. keyword 分词器将输入的字符串原封不动的输出。

  2. lowercase 分词过滤器将词汇单元转化为小写字母。

使用 大小写不敏感排序 分析器替换后,现在我们可以将其用在我们的复数域:

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "lower_case_sort": { (1)
          "type":     "string",
          "analyzer": "case_insensitive_sort"
        }
      }
    }
  }
}

PUT /my_index/user/1
{ "name": "Boffey" }

PUT /my_index/user/2
{ "name": "BROWN" }

PUT /my_index/user/3
{ "name": "bailey" }

GET /my_index/user/_search?sort=name.lower_case_sort
  1. name.lower_case_sort 域将会为我们提供大小写不敏感排序。

运行这个搜索请求会得到我们想要的文档排序: baileyBoffeyBROWN

但是这个顺序是正确的么?它符合我门的期望所以看起来像是正确的, 但我们的期望可能受到这个事实的影响:这本书是英文的,我们的例子中使用的所有字母都属于到英语字母表。

如果我们添加一个德语姓名 Böhm 会怎样呢?

现在我们的姓名会返回这样的排序: baileyBoffeyBROWNBöhmBöhm 会排在 BROWN 后面的原因是这些单词依然是按照它们表现的字节值排序的。 r 所存储的字节为 0x72 ,而 ö 存储的字节值为 0xF6 ,所以 Böhm 排在最后。每个字符的字节值都是历史的意外。

显然,默认排序顺序对于除简单英语之外的任何事物都是无意义的。事实上,没有完全“正确”的排序规则。这完全取决于你使用的语言。

语言之间的区别

每门语言都有自己的排序规则,并且 有时候甚至有多种排序规则。 这里有几个例子,我们前一小节中的四个名字在不同的上下文中是怎么排序的:

  • 英语: baileyboffeyböhmbrown

  • 德语: baileyboffeyböhmbrown

  • 德语电话簿: baileyböhmboffeybrown

  • 瑞典语: bailey, boffey, brown, böhm

Note

德语电话簿将 böhm 放在 boffey 的原因是 öoe 在处理名字和地点的时候会被看成同义词,所以 böhm 在排序时像是被写成了 boehm

Unicode 归类算法

归类是将文本按预定义顺序排序的过程。 Unicode 归类算法 或称为 UCA (参见 www.unicode.org/reports/tr10 ) 定义了一种将字符串按照在归类单元表中定义的顺序排序的方法(通常称为排序规则)。

UCA 还定义了 默认 Unicode 排序规则元素表 或称为 DUCETDUCET 为无论任何语言的所有 Unicode 字符定义了默认排序。如你所见,没有惟一一个正确的排序规则,所以 DUCET 让更少的人感到烦恼,且烦恼尽可能的小,但它还远不是解决所有排序烦恼的万能药。

而且,明显几乎每种语言都有自己的排序规则。大多时候使用 DUCET 作为起点并且添加一些自定义规则用来处理每种语言的特性。

UCA 将字符串和排序规则作为输入,并输出二进制排序键。 将根据指定的排序规则对字符串集合进行排序转化为对其二进制排序键的简单比较。

Unicode 排序

Tip

本节中描述的方法可能会在未来版本的 Elasticsearch 中更改。请查看 icu plugin 文档的最新信息。

icu_collation 分词过滤器默认使用 DUCET 排序规则。这已经是对默认排序的改进了。想要使用 icu_collation 我们仅需要创建一个使用默认 icu_collation 过滤器的分析器:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ducet_sort": {
          "tokenizer": "keyword",
          "filter": [ "icu_collation" ] (1)
        }
      }
    }
  }
}
  1. 使用默认 DUCET 归类。

通常,我们想要排序的字段就是我们想要搜索的字段, 因此我们使用与在 大小写敏感排序 中使用的相同的复数域方法:

PUT /my_index/_mapping/user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "sort": {
          "type": "string",
          "analyzer": "ducet_sort"
        }
      }
    }
  }
}

使用这个映射, name.sort 域将会含有一个仅用来排序的键。我们没有指定某种语言,所以它会默认会使用 DUCET collation

现在,我们可以重新索引我们的案例文档并测试排序:

PUT /my_index/user/_bulk
{ "index": { "_id": 1 }}
{ "name": "Boffey" }
{ "index": { "_id": 2 }}
{ "name": "BROWN" }
{ "index": { "_id": 3 }}
{ "name": "bailey" }
{ "index": { "_id": 4 }}
{ "name": "Böhm" }

GET /my_index/user/_search?sort=name.sort
Note

注意,每个文档返回的 sort 键,在前面的例子中看起来像 brownböhm ,现在看起来像天书: ᖔ乏昫တ倈⠀\u0001 。原因是 icu_collat​​ion 过滤器输出键 仅用于有效分类,不用于任何其他目的。

运行这个搜索请求反问的文档排序为: baileyBoffeyBöhmBROWN 。这个排序对英语和德语来说都正确,这已经是一种进步,但是它对德语电话簿和瑞典语来说还不正确。下一步我们为不同的语言自定义映射。

指定语言

可以为特定的语言配置使用归类表的 icu_collation 过滤器,例如一个国家特定版本的语言,或者像德语电话簿之类的子集。 这个可以按照如下所示通过使用 languagecountry 、 和 variant 参数来创建自定义版本的分词过滤器:

英语
{ "language": "en" }
德语
{ "language": "de" }
奥地利德语
{ "language": "de", "country": "AT" }
德语电话簿
{ "language": "de", "variant": "@collation=phonebook" }
Tip

你可以在一下网址阅读更多的 ICU 本地支持: http://userguide.icu-project.org/locale.

这个例子演示怎么创建德语电话簿排序规则:

PUT /my_index
{
  "settings": {
    "number_of_shards": 1,
    "analysis": {
      "filter": {
        "german_phonebook": { (1)
          "type":     "icu_collation",
          "language": "de",
          "country":  "DE",
          "variant":  "@collation=phonebook"
        }
      },
      "analyzer": {
        "german_phonebook": { (2)
          "tokenizer": "keyword",
          "filter":  [ "german_phonebook" ]
        }
      }
    }
  },
  "mappings": {
    "user": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "sort": { (3)
              "type":     "string",
              "analyzer": "german_phonebook"
            }
          }
        }
      }
    }
  }
}
  1. 首先我们为德语电话薄创建一个自定义版本的 icu_collation

  2. 之后我们将其包装在自定义的分析器中。

  3. 并且为我们的 name.sort 域配置它。

像我们之前那样重新索引并重新搜索:

PUT /my_index/user/_bulk
{ "index": { "_id": 1 }}
{ "name": "Boffey" }
{ "index": { "_id": 2 }}
{ "name": "BROWN" }
{ "index": { "_id": 3 }}
{ "name": "bailey" }
{ "index": { "_id": 4 }}
{ "name": "Böhm" }

GET /my_index/user/_search?sort=name.sort

现在返回的文档排序为: baileyBöhmBoffeyBROWN 。在德语电话簿归类中, Böhm 等同于 Boehm ,所以排在 Boffey 前面。

多排序规则

每种语言都可以使用复数域来支持对同一个域进行多规则排序:

PUT /my_index/_mapping/_user
{
  "properties": {
    "name": {
      "type": "string",
      "fields": {
        "default": {
          "type":     "string",
          "analyzer": "ducet" (1)
        },
        "french": {
          "type":     "string",
          "analyzer": "french" (1)
        },
        "german": {
          "type":     "string",
          "analyzer": "german_phonebook" (1)
        },
        "swedish": {
          "type":     "string",
          "analyzer": "swedish" (1)
        }
      }
    }
  }
}
  1. 我们需要为每个排序规则创建相应的分析器。

使用这个映射,只要按照 name.frenchname.germanname.swedish 域排序,就可以为法语、德语和瑞典语用户正确的排序结果了。不支持的语言可以回退到使用 name.default 域,它使用 DUCET 排序顺序。

自定义排序

icu_collation 分词过滤器提供很多选项,不止 languagecountry 、和 variant ,这些选项可以用于定制排序算法。可用的选项有以下作用:

  • 忽略变音符号

  • 顺序大写排先或排后,或忽略大小写

  • 考虑或忽略标点符号和空白

  • 将数字按字符串或数字值排序

  • 自定义现有归类或定义自己的归类

这些选项的详细信息超出了本书的范围,更多的信息可以查询 ICU plug-in documentationICU project collation documentation


书籍推荐