Elasticsearch文档只包含与术语列表相交但没有其他术语的术语

时间:2015-07-07 02:52:43

标签: elasticsearch

我的文件包含标签列表:


    {
       "fields": {
          "label": [
               "foo",
               "bar",
               "baz"
          ],
          "name": [
             "Document One"
          ],
          "description" : "A fine first document",
          "id" : 1
       }
    },
    {
       "fields": {
          "label": [
               "foo",
               "dog"
          ],
          "name": [
             "Document Two"
          ],
          "description" : "A fine second document",
          "id" : 2
       }
    }

我有一个术语列表:


    [ "foo", "bar", "qux", "zip", "baz"]

我想要一个查询,它将返回在术语列表中包含标签的文档 - 但没有其他术语。

因此,根据上面的列表,查询将返回Document One,但 Document Two(因为它的列表中不包含dog项条款。

我尝试使用not terms过滤器进行查询,如下所示:


    POST /documents/_search?size=1000
    {
       "fields": [
          "id",
          "name",
          "label"
       ],
       "filter": {
           "not": {
               "filter" : {
                   "bool" : {
                       "must_not": {
                          "terms": {
                             "label": [
                                "foo",
                                "bar",
                                "qux",
                                "zip",
                                "baz"
                             ]
                          }
                       }
                   }
               }
           }
       }
    }

但那没用。

如何创建一个查询,在给定术语列表的情况下,该查询将匹配仅包含列表中的术语的文档,而不包含其他术语?换句话说,所有文档都应包含标签列表,这些标签是所提供术语列表的子集。

2 个答案:

答案 0 :(得分:2)

我遵循了Rohit的建议,并实施了Elasticsearch script filter。您需要configure your Elasticsearch server to allow dynamic (inline) Groovy scripts

以下是Groovy脚本过滤器的代码:

def label_map = labels.collectEntries { entry -> [entry, 1] };
def count = 0;

for (def label : doc['label'].values) {
    if (!label_map.containsKey(label)) {
        return 0
    } else {
        count += 1
    }
};

return count

要在Elasticsearch查询中使用它,您需要转义所有换行符,或者将脚本放在一行上,如下所示:

def label_map = labels.collectEntries { entry -> [entry, 1] }; def count = 0; for (def label : doc['label'].values) { if (!label_map.containsKey(label)) { return 0 } else { count += 1 } }; return count

这是一个与我所做的非常类似的Elasticsearch查询,包括脚本过滤器:

POST /documents/_search
{
   "fields": [
      "id",
      "name",
      "label",
      "description"
   ],
   "query": {
      "function_score": {
         "query": {
            "filtered": {
               "query": {
                  "bool": {
                     "minimum_should_match": 1,
                     "should" : {
                        "term" : {
                           "description" : "fine" 
                        }
                     }
                 }
               },
               "filter": {
                  "script": {
                     "script": "def label_map = labels.collectEntries { entry -> [entry, 1] }; def count = 0; for (def label : doc['label'].values) { if (!label_map.containsKey(label)) { return 0 } else { count += 1 } }; return count",
                     "lang": "groovy",
                     "params": {
                        "labels": [
                           "foo", 
                           "bar", 
                           "qux", 
                           "zip", 
                           "baz"
                        ]
                     }
                  }
               }
            }
         },
         "functions": [
            {
               "filter": {
                  "query": {
                     "match": {
                        "label": "qux"
                     }
                  }
               },
               "boost_factor": 25
            }
         ],
         "score_mode": "multiply"
      }
   },
   "size": 10
}

我的实际查询需要将脚本过滤器与功能分数查询相结合,这很难弄清楚如何操作,因此我将此处作为示例包含在内。

这样做是使用脚本过滤器来选择其标签是查询中传递的标签子集的文档。对于我的用例(数千个文档,而不是数百万个),这个工作非常快 - 几十毫秒。

第一次使用脚本时,可能需要很长时间(约1000毫秒),这可能是由于编译和缓存造成的。但是后来的调用速度提高了100倍。

几点说明:

  • 我使用Sense console Chrome plugin来调试Elasticsearch查询。比在命令行上使用curl好多了! (请注意,Sense现在是Marvel的一部分,所以你也可以在那里得到它。
  • 为了实现Groovy脚本,我首先在笔记本电脑上安装了Groovy language,并编写了一些单元测试,并实现了脚本。一旦我确定该脚本正在运行,我将其格式化为适合一行并将其放入Sense。

答案 1 :(得分:1)

您可以使用脚本过滤器来检查数组术语是否包含文档中标签数组的所有值。我建议你制作一个单独的groovy文件或普通的javascript文件,把它放在config / scripts / folderToYourScript中,并在filter: { script : {script_file: file } }

的查询中使用它

在脚本文件中,您可以使用循环来检查需求