es中如何使用嵌套对象查询

最近公司的APP使用ES搜索功能时遇到一个需求——需要搜索出来的数据中只包含某个商户下的商品,且这些商品的库存都不为0。

首先我们搜索得到的文档格式简化后如下:
file
也就是说,此时,我的需求的搜索条件是:merchant_id=11,且stock不为0。如果按照需求来说,上面截图里的这个商品是不符合条件的,也就是不应该被搜索出来。但是如果按照es一般的filter写法去写,filter部分的写法应该是如下——

"filter":[
    { "terms":
            {"sku_list.merchant_id":[11]}
    },
    {"range":
            {"sku_list.stock":{"gte":1}}
    }
]

但这种写法会导致那个商品依然会被搜出来,原因是elasticsearch(lucene)使用的库没有内部对象的概念,因此内部对象被扁平化为一个简单的字段名称和值列表。也就是说,上面的商品文档,在es中会被转换为

{
"id": [ **** ],
"name": [ ****],
"title": [ **** ],
"post": [ **** ] ,
"sku_list.sku_id": [ 100, 101, 102],
"sku_list.stock": [ 2,0,3 ],
"sku_list.merchant_id": [ 10, 11, 12 ],
"sku_list.sale_price": [ 128.00 ]
}

所以sku_list里的stock跟merchant_id不再具有关联关系,因为整合后的对象满足了上面两个条件,所以可以被搜索出来。
要解决这个问题,我们需要对es的映射(mapping)进行一些小改动,将sku_list的type改为nested(嵌套数据类型,引用其他地方的一个说法:在内部,嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着可以独立于其他对象查询每个嵌套对象)。nested类型是对象数据类型的专用版本,它允许对象数组以可以彼此独立查询的方式进行索引。
file
那么如何将sku_list改为nested类型,又尽可能不影响线上已有的功能呢?
首先,可以查看当前索引下的所有映射以及映射类型。

curl -X GET "http://domain/my_index/_mapping"(domain是es所在服务器ip+端口,my_index换成对应的索引名称)

查看后发现sku_list对应的索引类型是text。但是es不允许直接修改或删除一个字段类型,所以通用的修改字段类型的解决办法是——

采用reindex的方法实现,就是创建一个新的mapping,里面的字段类型按照新的类型定义,然后使用reindex的方法把原来的数据拷贝到新的index下面

1、创建新的索引my_index_new

curl -X PUT "http://domain/my_index_new?pretty"

2、将索引的默认字段数调大(默认是1000,一般不需要调,但因为我们的文档比较复杂,字段数使用超过了1000,所以必须调整)

curl -X PUT "http://domain/my_index_new/_settings" -d '{"index.mapping.total_fields.limit": "3000"}'

3、将sku_list的字段类型设置为nested(注意这一步一定要再下一步同步数据前完成,不然同步完数据后,sku_list的字段类型又会被设置成了默认的text,就又无法再更改了)

curl -X POST "http://domain/my_index_new/_mapping" -d '{"properties": {"sku_list": {"type":"nested"}}}'  

4、将老数据同步到新数据

curl -X POST "http://domain/_reindex" -d '{ "source": {    "index": "my_index"  },  "dest": {    "index": "my_index_new"  }} '

如果原先数据量较大,第三步花的时间会比较长,需要耐心等待其同步完成。

5、删除原先索引(其实如果业务允许的话,可以直接在业务侧将索引改为my_index_new,老的索引就不删除,放着以防后面出现问题可以及时切换)

curl -X DELETE "http://domain/my_index"

6、设置原索引的别名(如果业务侧不方便改es接口处的索引,那只能通过4、5这种方法来确保原索引名正常使用,我们是直接用新的索引名称,老的不去动它)

curl -X POST "http://domain/_aliases" -d '{ "actions": [{"add":{"index":"my_index_new","alias":"my_index"}}]} '

如果上述步骤都正常完成,此时再查看索引的mapping,会发现sku_list已经是我们需要的nested类型了。

此时就可以使用nested的语法结构来构造查询语句。

{"query":{"bool":{"must":[{"dis_max":{"queries":[]}},{"nested":{"path":"sku_list","query":{"bool":{"must":[{"terms":{"sku_list.merchant_id":[11]}},{"range":{"sku_list.stock":{"gte":1}}}]}}}}],"filter":[]}}}

只展示了nested部分的结构,其他的可以根据实际情况替换。其中,nested里的path就是我们修改了类型的字段,另外就是注意nested在filter里使用貌似会报错,所以需要写在must查询条件里。

另外可以通过浏览器直接访问domain/_cat/indices?v,看到新的索引的信息,确认新索引的文档内存大小是否跟老的一致。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!