普通内部对象
"kibana_sample_data_ecommerce" : {
"mappings" : {
"properties" : {
"category" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword"
}
}
},
"currency" : {
"type" : "keyword"
},
"customer_full_name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
//省略部分
"products" : {!!!!!!!!!!!!!!!!!
"properties" : {
"_id" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"base_price" : {
"type" : "half_float"
},
"base_unit_price" : {
"type" : "half_float"
},
不是期望值
GET kibana_sample_data_ecommerce/_search
{
"query": {
"bool": {
"must": [
{ "match": { "products.base_price": 24.99 }},
{ "match": { "products.sku":"ZO0549605496"}},
{"match": { "order_id": "584677"}}
]
}
}
}
我这里搜索有三个条件,order_id,商品的价格和sku,事实上同时满足这三个条件的文档并不存在(sku=ZO0549605496的商品价格是11.99)。但是结果却返回了一个文档,这是为什么呢?
因为在ES中对于json对象数组的处理是压扁了处理的,比如上面的例子在ES存储的结构是这样的:
{
"order_id": [ 584677 ],
"products.base_price": [ 11.99, 24.99... ],
"products.sku": [ ZO0549605496, ZO0299602996 ],
...
}
很明显,这样的结构丢失了商品金额和sku的关联关系。
如果你的业务场景对这个问题不敏感,就可以选择这种方式,因为它足够简单并且效率也比下面两种方案高。
嵌套文档
// mapping
PUT test_index
{
"mappings": {
"properties": {
"user": {
"type": "nested",
"properties": {
"name": { "type": "string" },
"age": { "type": "short" }
}
}
}
}
}
// index data
PUT test_index/_doc/1
{
"group" : "root",
"user" : [
{
"name" : "John",
"age" : 30
},
{
"name" : "Alice",
"age" : 28
}
]
}
PUT test_index/_doc/2
{
"group" : "wheel",
"user" : [
{
"name" : "Tom",
"age" : 33
},
{
"name" : "Jack",
"age" : 25
}
]
}
// search
GET test_index/_search
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{ "match": { "user.name": "Tom" }},
{ "match": { "user.age": 33 }}
]
}
}
}
}
}
// result
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 2.2039728,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "2",
"_score" : 2.2039728,
"_source" : {
"group" : "wheel",
"user" : [
{
"name" : "Tom",
"age" : 33
},
{
"name" : "Jack",
"age" : 25
}
]
}
}
]
}
}
缺点
GET _cat/indices?v
是不是很奇怪问啥文档的数量是6而不是2呢?这是因为nested子文档在ES内部其实也是独立的lucene文档,只是我们在查询的时候,ES内部帮我们做了join处理。最终看起来好像是一个独立的文档一样。
那可想而知同样的条件下,这个性能肯定不如普通内部对象的方案。在实际的业务应用中要根据实际情况决定是否选择这种方案。
嵌套文档聚合
在查询的时候,我们使用 nested
查询 就可以获取嵌套对象的信息。同理, nested
聚合允许我们对嵌套对象里的字段进行聚合操作。
GET /my_index/blogpost/_search
{
"size" : 0,
"aggs": {
"comments": {
"nested": {
"path": "comments"
},
"aggs": {
"by_month": {
"date_histogram": {
"field": "comments.date",
"interval": "month",
"format": "yyyy-MM"
},
"aggs": {
"avg_stars": {
"avg": {
"field": "comments.stars"
}
}
}
}
}
}
}
}
// result
...
"aggregations": {
"comments": {
"doc_count": 4,
"by_month": {
"buckets": [
{
"key_as_string": "2014-09",
"key": 1409529600000,
"doc_count": 1,
"avg_stars": {
"value": 4
}
},
{
"key_as_string": "2014-10",
"key": 1412121600000,
"doc_count": 3,
"avg_stars": {
"value": 2.6666666666666665
}
}
]
}
}
}
...
总共有4个 comments 对象 :1个对象在9月的桶里,3个对象在10月的桶里。
TODO
例如,我们要基于评论者的年龄找出评论者感兴趣 tags
的分布。 comment.age
是一个嵌套字段,但 tags
在根文档中:
reverse_nested
父子文档
假如我需要更新文档的group属性的值,需要重新索引这个文档。尽管嵌套的user对象我不需要更新,他也随着主文档一起被重新索引了。
使用场景:一个子文档可以属于多个主文档的场景,用nested无法实现。
// my_join_field是给我们的父子文档关系的名字,这个可以自定义。join关键字表示这是一个父子文档关系,接下来relations里面表示question是父,answer是子。
PUT my_index
{
"mappings": {
"properties": {
"my_id": {
"type": "keyword"
},
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
// 索引父文档,"name": "question"表示插入的是父文档。
PUT my_index/_doc/1
{
"my_id": "1",
"text": "This is a question",
"my_join_field": {
"name": "question"
}
}
PUT my_index/_doc/2
{
"my_id": "2",
"text": "This is another question",
"my_join_field": {
"name": "question"
}
}
// 索引子文档,routing和parent要一致
PUT my_index/_doc/3?routing=1
{
"my_id": "3",
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
PUT my_index/_doc/4?routing=2
{
"my_id": "4",
"text": "This is another1 answer",
"my_join_field": {
"name": "answer",
"parent": "2"
}
}
// search
GET my_index/_search
{
"query": {
"match_all": {}
},
"sort": ["my_id"]
}
// 查询父文档 根据子文档条件
POST my_index/_search
{
"query": {
"has_child": {
"type": "answer",
"query": {
"match": {
"text": "answer"
}
}
}
}
}
// 查询子文档 根据父文档条件
POST my_index/_search
{
"query": {
"has_parent": {
"parent_type": "question",
"query": {
"match": {
"text": "question"
}
}
}
}
}
// 查询子文档 根据父id条件
POST my_index/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
总的来说,
嵌套对象通过冗余数据来提高查询性能,适用于读多写少的场景。
父子文档类似关系型数据库中的关联关系,适用于写多的场景,减少了文档修改的范围。
参考
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/query-dsl-parent-id-query.html