首页 > 解决方案 > 使用聚合结果过滤另一个索引中的文档

问题描述

我有 2 个索引:

假设users看起来像这样:

{
  "properties": {
    "cookies": {"type": "keyword"},
    "name": {"type": "text"}
  }
}

navigations看起来像这样:

{
  "properties": {
    "url": {"type": "keyword"},
    "cookie_id": {"type": "keyword"}
  }
}

正如您所注意到的,users并且navigations可以通过cookie_idandcookies字段连接在一起。

实际上,我的索引有更多字段,但只有这些是证明我的问题所必需的。

我将usersand存储navigations在 2 个不同的索引中,而不是使用join映射或nested映射,因为我将拥有navigations比用户更多的内容,并且在我的大多数搜索用例中,我只会搜索users,因此我不想维护navigationsper的列表users。我更喜欢将它们分开(我还有一些其他限制因素促使我选择 2 个单独的索引,例如数据协调等......)。

我想做的是这样的查询/聚合:“给我所有name Fabien导航5次的用户url http://example.com

到目前为止,我有以下查询/聚合(搜索查询是在我的 2 个索引上完成的):

发布 /users,navigations/_search

{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "Fabien"}}
      ]
    }
  },
  "aggregations": {
    "all_navs": {
        "global": {},
        "aggregations": {
            "cookies": {
                "terms": {"field": "cookie_id"},
                "aggregations": {
                    "page_visited": {
                        "filter": {
                            "bool": {
                                "must": [
                                    {"term": {"url": "http://example.com"} }
                                ]
                            }                           
                        },
                        "aggregations": {
                            "number_page_visited": {
                                "value_count": {"field": "type"}
                            }
                        }
                    },
                    "count_filter": {
                        "bucket_selector": {
                            "buckets_path": {
                                "count": "page_visited>number_page_visited"
                            },
                            "script": "params.count > 5"
                        }
                    }
                }
            }
        }
    }
  }
}

使用此查询,我可以过滤我users的 with name = Fabien,并且我可以从至少有 5 个文档 with 的地方获取cookie_id值。navigationsurl = http://example.com

但我不知道如何使用cookie_id聚合中的 s 来过滤我的users.

任何想法?

谢谢!

标签: elasticsearchelasticsearch-aggregation

解决方案


具有两个独立索引的解决方案

由于 elasticsearch 不是关系数据库,因此您将无法在单个请求中检索结果。这是elasticsearch的一个很大的限制,但这也是它表现出色的一个主要原因。

基本上,elasticsearch 会将您的查询编译为 Lucene 查询,并使用 Lucene 查询执行索引扫描。没有机制使查询中的某些参数(例如user_id字段的值)依赖于另一个查询的结果(例如,查找名称为“Fabien”的所有id值)。users

您必须在外部执行连接:

  • users首先,从name 为的索引中检索所有文档Fabien。如果文档数量不受限制,则必须执行滚动搜索或使用search_after

  • 其次,从第一个请求返回的文档集中的索引navigation中检索所有文档,user_id并且满足您的其他条件。

这种方法可能很慢,并且您无法保证在运行第二个查询时用户索引没有更新。

连接映射的解决方案

实际上,如果您使用连接类型映射,则不需要为您的用例使用聚合。

请注意,连接字段有几个限制,不建议将其作为建模一对多关系的默认解决方案。

这是一个可以满足您要求的工作示例。

映射:包含用户和导航字段以及连接字段。

PUT /user_navigation
{
    "mappings": {
        "properties": {
            "cookies": {
                "type": "keyword"
            },
            "name": {
                "type": "keyword"
            },
            "join_field": {
                "type": "join",
                "relations": {
                    "user": "navigation"
                }

            }
        }
    }
}

添加一些测试文档。两个父文档有name: Fabien,但只有一个有两个孩子cookies: http://example.com。另一个文档有两个孩子,cookies: http://example.com但没有用 命名Fabien

POST user_navigation/_doc/_bulk
{ "index" : { "_index" : "user_navigation", "_id" : "1" } }
{ "name" : "Fabien", "join_field": "user" }
{ "index" : { "_index" : "user_navigation", "_id" : "2" } }
{ "name" : "Fabien", "join_field": "user" }
{ "index" : { "_index" : "user_navigation", "_id" : "3" } }
{ "name" : "Autre", "join_field": "user" }
{ "index" : { "_index" : "user_navigation", "routing": "1" } }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "1"  }}
{ "index" : { "_index" : "user_navigation", "routing": "1"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "1"  }}
{ "index" : { "_index" : "user_navigation", "routing": "2"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "2"  }}
{ "index" : { "_index" : "user_navigation", "routing": "2"} }
{ "cookies": "other_url", "join_field": { "name": "navigation",  "parent": "3"  }}
{ "index" : { "_index" : "user_navigation", "routing": "3"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "3"  }}
{ "index" : { "_index" : "user_navigation", "routing": "3"} }
{ "cookies": "http://example.com", "join_field": { "name": "navigation",  "parent": "3"  }}

以下请求使用has_child 查询,将仅返回带有name: Fabien和 的文档,这样它至少有两个带有 的子文档cookies: http://example.com

GET user_navigation/_doc/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "name": "Fabien"
                    }
                },
                {
                    "has_child": {
                        "type": "navigation",
                        "query": {
                            "term": {
                                "cookies": "http://example.com"
                            }
                        },
                        "min_children": 2,
                        "inner_hits": {}
                    }
                }
            ]
        }
    }
}

响应将仅包含 id 为 1 的文档。

"min_children"参数允许更改必须满足请求的最小子文档数。

"inner_hits": {}允许在响应中检索子文档。


推荐阅读