大家在工作中想必也接触过Elasticsearch,今天介绍一下es中的嵌套对象及对应的查询方式。
从考虑一个业务场景开始吧,业务上需要把某些类似的商品聚合成为一个关联组,需要支持根据某个商品的特征,查询到它所在的关联组,es中的存储结构如下:
{
"memberGoods":[
{
"title":"商品A",
"brand":"a"
},
{
"title":"商品B",
"brand":"b"
}
],
"groupId":"A"
}
那么问题来了,如果memberGoods是一个普通的Object类型,对于下面的查询条件:
{
"query":{
"bool":{
"must":[
{
"match":{
"title":"商品A"
}
},
{
"match":{
"brand":"b"
}
}
]
}
}
}
上面的数据依然会匹配上,但是商品A的品牌应该是a,而不是b呀,造成这种现象的原因是结构性的JSON文档会平整成索引内的一个简单键值格式,就像这样:
{
"memberGoods.title":[
"商品A",
"商品B"
],
"memberGoods.brand":[
"a",
"b"
],
"groupId":"A"
}
显然,如上的数据损失了同一商品数据之间的关联性,从而出现了交叉匹配的现象,为解决这一问题,nested Object应运而生,它保留了子文档数据中的关联性,如果memberGoods的数据格式被定义为nested,那么每一个nested object 将会作为一个隐藏的单独文本建立索引。如下:
{
"groupId":"A"
},
{
"memberGoods.title":"商品A",
"memberGoods.brand":"a"
},
{
"memberGoods.title":"商品B",
"memberGoods.brand":"b"
}
通过分开给每个nested object建索引,object内部的字段间的关系就能保持。当执行查询时,只会匹配’match’同时出现在相同的nested object的结果。
不仅如此,由于nested objects 建索引的方式,在查询的时候将根文本和nested文档拼接是很快的,就跟把他们当成一个单独的文本一样的快。
nested object作为一个独立隐藏文档单独建索引,因此,我们不能直接查询它们。取而代之,我们必须使用nested查询或者nested filter来接触它们,java语言描述如下:
queryBuilder.must(QueryBuilders.nestedQuery("memberGoods"/** nested 字段*/,
QueryBuilders.matchPhraseQuery("memberGoods.title", "商品A"), ScoreMode.Avg));
其中memberGoods是父字段,memberGoods.title是子字段,以上已有提及,最后的参数ScoreMode.Avg是父文档匹配分数的设定,(Parent hit's score is the average/max/sum/min of all child scores.)
此外,nested形式的查询也有一些需要注意的缺点:
1.增加,改变或者删除一个nested文本,整个文本必须重新建索引。nested文本越多,代价越大。
2.检索请求会返回整个文本,而不仅是匹配的nested文本。尽管有计划正在执行以能够支持返回根文本的同时返回最匹配的nested文本,但目前还未实现。
如果原来类型不是nested,可以使用elasticsearch动态模板设置字段类型nested
{
"index_patterns": [
"*"
],
"order": 0,
"version": 1,
"mappings": {
"dynamic_templates": [
{
"nested_fields": {
"match": "*_nested",
"mapping": {
"type": "nested"
}
}
}
]
}
}