使用的es 版本6.x
最近的工作中需要对es中nested字段进行空值查询?原以为很简单的事情,却几经周折。下面把整个心历路程总结一下,希望后来的同人少走弯路。
如何进行空值查询,网上一搜一大把,不约而同的说采用es exists关键字进行空值过滤。
按照网上的说法,测试了一下
GET test_nested_obj_eg/_doc/_search
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "tag_list"
}
}
}
}
}
为啥我使用exists进行空值null过滤却不生效呢?
查询语句中的tag_list字段是nested类型,会不会和这个有关系?
作为验证,我使用了一个非nested类型的字段,应用同样的语句是生效的。那问题就出在我是用的是nested字段,这个问题该怎么解呢?
nested类型字段的查询方式和普通的字段查询方式不一样,会不会和这个有关系呢?
按照nested查询规则尝试一下
GET test_nested_obj_eg/_doc /_search
{
"query": {
"nested": {
"path": "tag_list",
"query": {
"bool": {
"must": {
"exists": {
"field": "tag_list"
}
}
}
}
}
}
}
这个语句仅返回了存在tag_list字段的所有记录,符合预期。看到曙光,那获取不含tag_list字段的文档数据不就把must改成must_not。尝试如下
GET test_nested_obj_eg/_doc/_search
{
"query": {
"nested": {
"path": "tag_list",
"query": {
"bool": {
"must_not": {
"exists": {
"field": "tag_list"
}
}
}
}
}
}
}
这么调整后,什么结果都没有,比较坑,为什么must_not不工作?网上查询了一些资料后,“无功而返”。
es不是有功能强大的script语法,能否从script的方向寻找突破?
经过一番资料查询,发现script脚本功能分为两类。
1. 可以嵌入到query查询语句的script语法
2. 不能嵌入到query查询语句的script语法
之前用过嵌入到query查询语句的script语法,好吧先试一下
GET test_nested_obj_eg/_doc/_search
{
"query": {
"bool": {
"must": {
"script": {
"script": {
"source": "doc['tag_list'].size() == 0",
"lang": "painless"
}
}
}
}
}
}
执行报错
No field found for [tag_list] in mapping with types [_doc]
怀疑是部分文档缺少tag_list字段,应用这种查询前需要检查当前doc中是否存在tag_list这个key。
又在网上搜了一圈,发现doc对象还真有containsKey()成员函数,暗暗自喜,语句如下,
GET test_nested_obj_eg/_doc/_search
{
"query": {
"bool": {
"must": {
"script": {
"script": {
"source": "if(doc.containsKey('tag_list')){return doc.tag_list.size() == 0}else{ return true}",
"lang": "painless"
}
}
}
}
}
}
有句俗话说,希望也大,失望也也大!执行结果非预期,并且多次尝试后,最终确认doc.containsKey()函数对nested类型的字段不起效果,总是返回false。
作为对比,测试了一下非nested类型字段,doc.containsKey()函数是生效的。为什么doc.containsKey()函数对nested类型字段不生效呢?
查阅官网有如下信息:
doc[...]符号仅允许简单值类型字段(不能够从这个符号中返回json对象),换句话说仅对于非分析字段或者基于简单类型的字段,
也就是doc对nested类型字段都不生效,直接放弃这种方式。
再试一下不能嵌入到query查询语句的script语法
有两种写法
GET /_search
{
"query" : {
"match_all": {}
},
"script_fields" : {
"test1" : {
"script" : {
"lang": "painless",
"source": "doc['tag_list'].value"
}
}
}
}
或者
GET /_search
{
"query" : {
"match_all": {}
},
"script_fields" : {
"test1" : {
"script" : "params['_source']['tag_list']"
}
}
}
官网明确标注了这两种语法的差异:
It’s important to understand the difference between doc['my_field'].value and params['_source']['my_field']. The first, using the doc keyword, will cause the terms for that field to be loaded to memory (cached), which will result in faster execution, but more memory consumption. Also, the doc[...] notation only allows for simple valued fields (you can’t return a json object from it) and makes sense only for non-analyzed or single term based fields. However, using doc is still the recommended way to access values from the document, if at all possible, because _source must be loaded and parsed every time it’s used. Using _source is very slow.
大意如下:
理解doc['my_field'].value 和 params['_source']['my_field']之间的差异是非常重要的。
首先,使用doc关键字会引起检索的字段被加载到内存中,这样的结果就是获得更快的执行体验,但代价是更多的内存消耗,另外,doc[...]符号仅允许简单值类型字段(不能够从这个符号中返回json对象),换句话说仅对于非分析字段或者基于简单类型的字段,几乎在所有的情况下,然而使用doc获取字段值仍然是被推荐的做法,原因是如果使用_source,则每次一定被加载和解析。使用_source是非常慢的。
不管使用哪一种语法,script_fields均无法嵌入query查询子句,原因是query查询子句在查询阶段执行,而script_fields在获取阶段
尝试以下语法
GET test_nested_obj_eg/_doc/_search
{
"script_fields": {
"source": {
"script": {
"source": "if(params._source.containsKey(‘tag_list‘)){params._source.tag_list.size() == 0}"
}
}
}
}
本以为这种带条件的执行语句,只会返回script执行结果为true的文档数据,然后这个脚本并不会过滤执行结果为false的文档数据,
而是简单的把script的执行结果作为返回值进行展示而已。也就是,这种语法无法过滤数据集,并不是期望的。
脚本的思路也行不通,怎么办?
到这个点,心理有点发慌了,担心搞不定了。
最后又回到exists语法上,心里面一直觉得不可能只有must起作用。经过仔细研究nested嵌套查询语法,最后把
GET test_nested_obj_eg/_doc/_search
{
"query": {
"nested": {
"path": "tag_list",
"query": {
"bool": {
"must_not": {
"exists": {
"field": "tag_list"
}
}
}
}
}
}
}
查询语句中的must_not移动了一下位置,放到了nested查询语句的前面
GET test_nested_obj_eg/_doc/_search
{
"query": {
"bool": {
"must_not": [{
"nested": {
"path": "tag_list",
"query": {
"exists": {
"field": "tag_list"
}
}
}
}]
}
}
}
终于达到预期效果了,真是激动万分。
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-script-fields.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-query.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html
https://discuss.elastic.co/t/must-not-not-working-with-exist-in-nested-query/101952