https://es.xiaoleilu.com/052_Mapping_Analysis/00_Intro.html
概念
映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型(string, number, booleans, date等)。
分析(analysis)机制用于进行全文文本(Full Text)的分词,以建立供搜索用的反向索引。
数据类型差异
1 | GET /gb/_mapping |
返回:
1 | { |
2 | "gb" : { |
3 | "mappings" : { |
4 | "properties" : { |
5 | "date" : { |
6 | "type" : "date" |
7 | }, |
8 | "email" : { |
9 | "type" : "text", |
10 | "fields" : { |
11 | "keyword" : { |
12 | "type" : "keyword", |
13 | "ignore_above" : 256 |
14 | } |
15 | } |
16 | }, |
17 | "name" : { |
18 | "type" : "text", |
19 | "fields" : { |
20 | "keyword" : { |
21 | "type" : "keyword", |
22 | "ignore_above" : 256 |
23 | } |
24 | } |
25 | }, |
26 | "tweet" : { |
27 | "type" : "text", |
28 | "fields" : { |
29 | "keyword" : { |
30 | "type" : "keyword", |
31 | "ignore_above" : 256 |
32 | } |
33 | } |
34 | }, |
35 | "user_id" : { |
36 | "type" : "long" |
37 | }, |
38 | "username" : { |
39 | "type" : "text", |
40 | "fields" : { |
41 | "keyword" : { |
42 | "type" : "keyword", |
43 | "ignore_above" : 256 |
44 | } |
45 | } |
46 | } |
47 | } |
48 | } |
49 | } |
50 | } |
查询:
1 | GET /gb/_search?q=2019 # 返回空 |
2 | GET /gb/_search?q=2019-12-12 # 返回3个结果 |
3 | GET /gb/_search?q=date:2019-12-12 # 返回3个结果 |
4 | GET /gb/_search?q=date:2019 # 返回空 |
可以,是因为date被es推测为date类型,而_all里是string类型,只会完整匹配2019-12-12。
确切值VS全文索引
确切值 及 全文文本:
确切值是确定的,正如它的名字一样。比如一个date或用户ID,也可以包含更多的字符串比如username或email地址。
确切值”Foo”和”foo”就并不相同。确切值2014和2014-09-15也不相同。
全文文本,从另一个角度来说是文本化的数据(常常以人类的语言书写),比如一篇推文(Twitter的文章)或邮件正文。
全文文本常常被称为非结构化数据,其实是一种用词不当的称谓,实际上自然语言是高度结构化的。
问题是自然语言的语法规则是如此的复杂,计算机难以正确解析。例如这个句子:
May is fun but June bores me.
到底是说的月份还是人呢?
确切值是很容易查询的,因为结果是二进制的 – 要么匹配,要么不匹配。下面的查询很容易以SQL表达:
1 | WHERE name = "John Smith" |
2 | AND user_id = 2 |
3 | AND date > "2014-09-15" |
倒排索引
单词 -》 文档Id
分析
- 当你查询全文(full text)字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表。
- 当你查询一个确切值(exact value)字段,查询将不分析查询字符串,但是你可以自己指定。
测试分析器
尤其当你是Elasticsearch新手时,对于如何分词以及存储到索引中理解起来比较困难。为了更好的理解如何进行,你可以使用analyze API来查看文本是如何被分析的。在查询字符串参数中指定要使用的分析器,被分析的文本做为请求体:
1 | GET /_analyze?analyzer=standard&text=Text to analyze |
结果中每个节点在代表一个词:
1 | { |
2 | "tokens": [ |
3 | { |
4 | "token": "text", |
5 | "start_offset": 0, |
6 | "end_offset": 4, |
7 | "type": "<ALPHANUM>", |
8 | "position": 1 |
9 | }, |
10 | { |
11 | "token": "to", |
12 | "start_offset": 5, |
13 | "end_offset": 7, |
14 | "type": "<ALPHANUM>", |
15 | "position": 2 |
16 | }, |
17 | { |
18 | "token": "analyze", |
19 | "start_offset": 8, |
20 | "end_offset": 15, |
21 | "type": "<ALPHANUM>", |
22 | "position": 3 |
23 | } |
24 | ] |
25 | } |
这个例子在es7.5版本报错。
为了手动指定特定字段的分析器,我们必须通过映射(mapping)人工设置这些字段。
映射
在上面查询映射中,GET /gb/_mapping返回字段的映射关系。
index参数控制字符串以何种方式被索引。它包含以下三个值当中的一个:
- analyzed 首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。
- not_analyzed 索引这个字段,使之可以被搜索,但是索引内容和指定值一样。不分析此字段。
- no 不索引这个字段。这个字段不能为搜索到。
对于analyzed类型的字符串字段,使用analyzer参数来指定哪一种分析器将在搜索和索引的时候使用。默认的,Elasticsearch使用standard分析器,但是你可以通过指定一个内建的分析器来更改它,例如whitespace、simple或english。
1 | { |
2 | "tweet": { |
3 | "type": "string", |
4 | "analyzer": "english" |
5 | } |
6 | } |
更新映射
你可以在第一次创建索引的时候指定映射的类型。此外,你也可以晚些时候为新类型添加映射(或者为已有的类型更新映射)。
重要:
你可以向已有映射中增加字段,但你不能修改它。如果一个字段在映射中已经存在,这可能意味着那个字段的数据已经被索引。如果你改变了字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。
复合类型
多值字段
我们想让tag字段包含多个字段,这非常有可能发生。我们可以索引一个标签数组来代替单一字符串:
{ “tag”: [ “search”, “nosql” ]}
对于数组不需要特殊的映射。任何一个字段可以包含零个、一个或多个值,同样对于全文字段将被分析并产生多个词。
言外之意,这意味着数组中所有值必须为同一类型。你不能把日期和字符窜混合。如果你创建一个新字段,这个字段索引了一个数组,Elasticsearch将使用第一个值的类型来确定这个新字段的类型。
当你从Elasticsearch中取回一个文档,任何一个数组的顺序和你索引它们的顺序一致。你取回的_source字段的顺序同样与索引它们的顺序相同。
然而,数组是做为多值字段被索引的,它们没有顺序。在搜索阶段你不能指定“第一个值”或者“最后一个值”。倒不如把数组当作一个值集合(bag of values)
空字段
当然数组可以是空的。这等价于有零个值。事实上,Lucene没法存放null值,所以一个null值的字段被认为是空字段。
这四个字段将被识别为空字段而不被索引:
“empty_string”: “”,
“null_value”: null,
“empty_array”: [],
“array_with_null_value”: [ null ]
多层对象
我们需要讨论的最后一个自然JSON数据类型是对象(object)——在其它语言中叫做hash、hashmap、dictionary 或者 associative array.
内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象。例如,做为在tweet文档中user_name和user_id的替代,我们可以这样写:
1 | { |
2 | "tweet": "Elasticsearch is very flexible", |
3 | "user": { |
4 | "id": "@johnsmith", |
5 | "gender": "male", |
6 | "age": 26, |
7 | "name": { |
8 | "full": "John Smith", |
9 | "first": "John", |
10 | "last": "Smith" |
11 | } |
12 | } |
13 | } |
内部对象的映射
Elasticsearch 会动态的检测新对象的字段,并且映射它们为 object 类型,将每个字段加到 properties 字段下
1 | { |
2 | "gb": { |
3 | "tweet": { <1> |
4 | "properties": { |
5 | "tweet": { "type": "string" }, |
6 | "user": { <2> |
7 | "type": "object", |
8 | "properties": { |
9 | "id": { "type": "string" }, |
10 | "gender": { "type": "string" }, |
11 | "age": { "type": "long" }, |
12 | "name": { <3> |
13 | "type": "object", |
14 | "properties": { |
15 | "full": { "type": "string" }, |
16 | "first": { "type": "string" }, |
17 | "last": { "type": "string" } |
18 | } |
19 | } |
20 | } |
21 | } |
22 | } |
23 | } |
24 | } |
25 | } |
<1> 根对象.
<2><3> 内部对象.
对user和name字段的映射与tweet类型自己很相似。事实上,type映射只是object映射的一种特殊类型,我们将 object 称为根对象。它与其他对象一模一样,除非它有一些特殊的顶层字段,比如 _source, _all 等等。
内部对象是怎样被索引的
Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。 为了让 Elasticsearch 可以有效的索引内部对象,将文件转换为以下格式:
1 | { |
2 | "tweet": [elasticsearch, flexible, very], |
3 | "user.id": [@johnsmith], |
4 | "user.gender": [male], |
5 | "user.age": [26], |
6 | "user.name.full": [john, smith], |
7 | "user.name.first": [john], |
8 | "user.name.last": [smith] |
9 | } |
内部对象数组
最后,一个包含内部对象的数组如何索引。 我们有个数组如下所示:
1 | { |
2 | "followers": [ |
3 | { "age": 35, "name": "Mary White"}, |
4 | { "age": 26, "name": "Alex Jones"}, |
5 | { "age": 19, "name": "Lisa Smith"} |
6 | ] |
7 | } |
此文件会如我们以上所说的被扁平化,但其结果会像如此:
1 | { |
2 | "followers.age": [19, 26, 35], |
3 | "followers.name": [alex, jones, lisa, smith, mary, white] |
4 | } |
{age: 35}与{name: Mary White}之间的关联会消失,因每个多值的栏位会变成一个值集合,而非有序的阵列。 这让我们可以知道:
- 是否有26岁的追随者?
但我们无法取得准确的资料如:
- 是否有26岁的追随者且名字叫Alex Jones?
关联内部对象可解决此类问题。