使用聚合结果过滤另一个索引中的文档

Using results of an aggregation to filter documents in another index

我有 2 个索引:

假设 users 看起来像这样:

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

navigations 看起来像这样:

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

如您所见,usersnavigations 可以通过 cookie_idcookies 字段连接在一起。

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

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

我想做的是 query/aggregation 这样的:"give me all users with name Fabien that navigated 5 times on url http://example.com"

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

POST /用户,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"
                        }
                    }
                }
            }
        }
    }
  }
}

通过此查询,我可以使用 name = Fabien 过滤我的 users,并且我可以从 navigations 中获取 cookie_id 值,其中至少有 5 个文档具有 url = http://example.com.

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

有什么想法吗?

谢谢!

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

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

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

您必须在外部执行连接:

  • 首先,从名称为 Fabien 的索引 users 中检索所有文档。如果文档数量不受限制,则必须执行 scroll search or use search_after

  • 其次,从索引 navigation 检索所有文档,其中 user_id 在第一个请求 return 的文档集中,您的其他条件是满意。

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

连接映射的解决方案

实际上,如果您使用 join type mapping,则不需要为您的用例使用聚合。

请注意,连接字段有 several restriction,不推荐作为一对多关系建模的默认解决方案。

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

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

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

            }
        }
    }
}

添加一些测试文件。两个 parent 文档有 name: Fabien,但只有一个有两个 children 和 cookies: http://example.com。另一个文件有两个 children 和 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 query 并将 return 仅包含 name: Fabien 的文档,并且它至少有两个 children 包含 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" 参数允许更改必须满足请求的最小 children 文档数。

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