Elasticsearch - 查找作为匹配用户标签子集的商品标签

时间:2017-11-24 05:32:56

标签: elasticsearch elasticsearch-5

PUT /goods/goods/1
{
  "tag": ["apple","gold","4G"]
}

PUT /goods/goods/2
{
  "tag": ["apple","gold"]
}

有货物有多个标签,我们也有多个标签的用户。我们试图找到标签是用户标签子集的商品。

例如:

user ["apple","gold","4G","fruit"] find goods 1 2
user ["apple","gold","4G"] find goods 1 2
user ["apple","gold"] find goods 2
user ["apple"] find nothing

Elasticsearch能实现吗?

1 个答案:

答案 0 :(得分:0)

您希望实现的行为类似于Finding Multiple Exact Values中描述的行为。

由于你展示的最后一个例子,这不是直接可能的:

user ["apple"] find nothing

Elasticsearch必须构建一个查询find me all goods that have only one tag 'apple',并且不可能直接用倒排索引实现。它只存储包含该标签的文档的ID;此列表将包含所有包含该标记的文档,无论它们是否还包含其他标记。有关详细信息,请参阅此page

虽然Exact Match有一种解决方法,但在您的情况下,您希望在Elasticsearch中检索作为子集的文档,即包含查询中的任意数量的元素,但仅此而已。与完全匹配的情况类似,它无法有效地完成。

脚本化解决方案

如果您的数据允许,可能会出现解决方法。例如,如果您的商品/用户标签矩阵稀疏(意味着他们很少共享相同的标签),您可以使用带有脚本过滤器的bool should查询来实现目标:

POST goods/goods/_search
{
  "query": {
    "bool": {
      "filter": {
        "bool": {
          "must": [
            {
              "bool": {
                "should": [
                  {
                    "term": {
                      "tag": {
                        "value": "apple"
                      }
                    }
                  },
                  {
                    "term": {
                      "tag": {
                        "value": "gold"
                      }
                    }
                  },
                  {
                    "term": {
                      "tag": {
                        "value": "4G"
                      }
                    }
                  }
                ]
              }
            },
            {
              "script": {
                "script": {
                  "inline": "int k = 0; for (int i = 0; i < doc['tag'].length; ++i) { if (!params.user_tags.contains(doc['tag'][i])) { k +=1}   }   k == 0",
                  "lang": "painless",
                  "params": {
                    "user_tags": [
                      "apple",
                      "gold",
                      "4G"
                    ]
                  }
                }
              }
            }
          ]
        }
      }
    }
  }
}

查询执行以下操作(以简化方式):

  • should部分:匹配包含任何用户标签的所有文档;
  • script部分:过滤掉包含其他标记的内容

script查询允许执行几乎任何可能的查询,但它肯定不会像内置term查询那样高效(因为它不会使用索引,而是会执行完整的查询 - 在最坏的情况下扫描。)

我们的想法是同时使用termscript并在AND(bool must查询中)将它们统一起来,因此脚本部分仅应用于可能匹配的文档。 (关于在Elasticsearch中应用查询的顺序的注释可以找到here。)

最后的注释

与Elasticsearch一样,为了获得最佳性能,您可能需要重新排列数据(例如添加tag_count等)。试着找出最适合你的。

希望有所帮助!