Elasticsearch 嵌套对象之嵌套类型

nested 类型是一种特殊的对象 object 数据类型,允许对象数组彼此独立地进行索引和查询。

对象数组如何扁平化

内部对象 object 字段的数组不能像我们期望的那样工作。Lucene 没有内部对象的概念,所以 Elasticsearch 将对象层次结构扁平化为一个字段名称和值简单列表。例如,以下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-type: application/json' -d '
{
"group": "fans",
"user": [
{
"first": "John",
"last": "Smith"
},
{
"first": "Alice",
"last": "White"
}
]
}
'

说明

user 字段被动态的添加为 object 类型的字段。

在内部其转换成一个看起来像下面这样的文档:

1
2
3
4
5
{
"group": "fans",
"user.first": ["alice", "john"],
"user.last": ["smith", "white"]
}

user.firstuser.last 字段被扁平化为多值字段,并且 alice 和 white 之间的关联已经丢失。本文档将错误地匹配 user.first 为 alice 和 user.last 为 smith 的查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
curl -XGET 'localhost:9200/my_index/_search?pretty' -H 'Content-Type: application/json' -d '
{
"query": {
"bool": {
"must": [
{
"match": {
"user.first": "Alice"
}
},
{
"match": {
"user.last": "Smith"
}
}
]
}
}
}
'

对对象数组使用嵌套字段

如果需要索引对象数组并维护数组中每个对象的独立性,则应使用 nested 数据类型而不是 object 数据类型。在内部,嵌套对象将数组中的每个对象作为单独的隐藏文档进行索引,这意味着每个嵌套对象都可以使用嵌套查询 nested query 独立于其他对象进行查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d '
{
"mappings": {
"my_type": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
}
'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d '
{
"group": "fans",
"user": [
{
"first": "John",
"last": "Smith"
},
{
"first": "Alice",
"last": "White"
}
]
}
'

说明:

user 字段映射为 nested 类型,而不是默认的 object 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
curl -XGET 'localhost:9200/my_index/_search?pretty' -H 'Content-Type: application/json' -d '
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{
"match": { "user.first": "Alice"}
},
{
"match": { "user.last": "Smith" }
}
]
}
}
}
}
}
'

说明:

此查询得不到匹配,是因为 Alice 和 Smith 不在同一个嵌套对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
curl -XGET 'localhost:9200/my_index/_search?pretty' -H 'Content-Type: application/json' -d '
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{ "match": { "user.first": "Alice" } },
{ "match": { "user.last": "White" } }
]
}
},
"inner_hits": {
"highlight": {
"fields": {
"user.first": {}
}
}
}
}
}
}
'

说明:

此查询得到匹配,是因为 Alice 和 White 位于同一个嵌套对象中。

inner_hits 允许我们突出显示匹配的嵌套文档。

嵌套文档可以:

  • 使用 nested 查询进行查询
  • 使用 nested 和 reverse_nested 聚合进行分析
  • 使用 nested 排序进行排序
  • 使用 nested_inner_hits 进行检索与突出显示

嵌套字段参数

嵌套字段接受以下参数:

  • dynamic:是否将新属性动态添加到现有的嵌套对象。共有 true(默认),false 和 strict 三种参数。
  • include_in_all:(_all 字段已经废弃了)
  • properties:嵌套对象中的字段,可以是任何数据类型,包括嵌套。新的属性可能会添加到现有的嵌套对象。

备注:

类型映射(type mapping)、对象字段和嵌套字段包含的子字段,称之为属性 properties。这些属性可以为任意数据类型,包括 object 和 nested。属性可以通过以下方式加入:

  • 当在创建索引时显式定义它们
  • 当使用 PUT mapping API 添加或更新映射类型时显式地定义它们
  • 当索引包含新字段的文档时动态的加入

重要:

由于嵌套文档作为单独的文档进行索引,因此只能在 nested 查询,nested/reverse_nested 聚合或者 nested_inner_hits 的范围内进行访问。

限制嵌套字段的个数

索引一个拥有 100 个嵌套字段的文档,相当于索引了 101 个文档,因为每一个嵌套文档都被索引为一个独立的文档。为了防止不明确的映射,每个索引可以定义的嵌套字段的数量已被限制为 50 个。

多嵌套查询方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"query": {
"bool": {
"must": [
{
"nested": {
"path": [
"ajxx"
],
"query": {
"bool": {
"must": [
{
"match": {
"ajxx.ajzt": "破案"
}
},
{
"range": {
"ajxx.sasj": {
"gte": "2017-01-01 12:10:10",
"lte": "2017-01-02 12:10-40"
}
}
}
],
"should": [
{
"query_string": {
"query": "20170316盗窃案"
}
}
]
}
}
}
}
]
}
}
}

查询字段名称的模糊匹配编辑
字段名称可以用模糊匹配的方式给出:任何与模糊式正则匹配的字段都会被包括在搜索条件中。

1
2
3
4
5
6
{
"multi_match": {
"query": "结果",
"fields": ["*", "*_title"]
}
}

当这些子字段出现数值类型的时候,就会报异常了,解决方法是加入 lenient 字段。

1
2
3
4
5
6
7
8
9
10
11
12
{
"type": "parse_exception",
"reason": "failed to parse date field [XXX] with format [yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis]"
}

{
"multi_match": {
"query": "结果",
"lenient": "true",
"fields": ["*"]
}
}

利用 multi_match 嵌套全文检索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"include_in_parent": true,
"include_in_root": true,
{
"query": {
"bool": {
"must": [
{
"match": {
"ajztmc": "立案"
}
},
{
"match": {
"zjasl": "刑事"
}
},
{
"range": {
"lasj": {
"gte": "2015-01-01 12:10:10",
"lte": "2016-01-01 12:10:40"
}
}
},
{
"nested": {
"path": [
"rqxxx"
],
"query": {
"bool": {
"must": [
{
"match": {
"rqxx.baqmc": "办案区名称"
}
}
]
}
}
}
},
{
"nested": {
"path": [
"saryxx"
],
"query": {
"bool": {
"must": [
{
"match": {
"abc": "嫌疑人"
}
},
{
"match": {
"def": "女"
}
}
]
}
}
}
},
{
"nested": {
"path": [
"wp"
],
"query": {
"bool": {
"must": [
{
"match": {
"wp.wpzlnc": "赃物"
}
},
{
"match": {
"wp.wpztmc": "物品入库"
}
}
]
}
}
}
},
{
"multi_match": {
"query": "男",
"lenient",
"fields": [
"*"
]
}
}
]
}
},
"from": 0,
"size": 100,
"sort": {
"zxxgsj": {
"order": "desc"
}
}
}