【问题标题】:Elasticsearch aggregation over children document field valuesElasticsearch 聚合子文档字段值
【发布时间】:2020-11-14 14:56:33
【问题描述】:

我面临以下基于其子文档的聚合值选择和排序父文档的问题。聚合(例如 sum)本身取决于查询字符串,即哪些子文档与聚合相关。

示例:给定文档 basket Abasket B,对于每个 basket document,我希望对其 fruitnumber 字段求和如果name 字段与我的查询匹配,则为孩子,例如apples.

PUT /baskets/_doc/0
{
  "name": "basket A", 
  "fruit": [
    {
      "name": "apples",
      "number": 2
    },
    {
      "name": "oranges",
      "number": 3
    }
  ]
}

PUT /baskets/_doc/1
{
  "name": "basket B",
  "fruit": [
    {
      "name": "apples",
      "number": 3
    },
    {
      "name": "apples",
      "number": 3
    }
  ]
}

映射:

PUT /baskets
{
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "fruit": { 
        "type": "nested",
        "properties": {
          "name": { "type": "text" },
          "number": { "type": "long" }
        }
      }
    }
  }
}
  • 用例 1:哪个篮子有(严格)超过 5 个苹果?预计只有 篮子 B
  • 用例 2:按苹果数量对篮子进行排序。预计 篮 B 共有 6 个苹果,然后 篮 A 共有 2 个苹果。

如何使用 Elasticsearch (7.8.0) 查询 DSL 来实现这一点?

到目前为止,我已经尝试使用nested queries and aggregations,但没有成功。

谢谢!

编辑:添加映射

编辑:更新了数字以更好地反映问题

*编辑:为 用例 2 添加了可能的答案(请参阅@joe 的答案的 cmets):

GET /profiles/_search
{
  "aggs": {
    "aggs_baskets": {
      "terms": {
        "field": "name",
        "order": {"nest > fruit_filter > fruit_sum": "desc"}
      },
      "aggs": {
        "nest":{
          "nested":{
            "path": "fruit"
          },
          "aggs":{
            "fruit_filter":{
              "filter": {
                "term": {"fruit.name": "apple"}
              },
              "aggs":{
                "fruit_sum":{
                  "sum": {"field": "fruit.number"}
                }
              }
            }
          }
        }
      }
    }
  }
}

【问题讨论】:

  • 发布您的映射(或生成的映射),您绝对可以使用嵌套查询来做到这一点。

标签: elasticsearch


【解决方案1】:

用例 1:

GET baskets/_search
{
  "query": {
    "nested": {
      "path": "fruit",
      "inner_hits": {}, 
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "fruit.name": {
                  "value": "apples"
                }
              }
            },
            {
              "range": {
                "fruit.number": {
                  "gte": 5
                }
              }
            }
          ]
        }
      }
    }
  }
}

严格超过 5 个 --> gt; >=5 --> gte.

还请注意inner_hits 部分——这为您提供了导致此特定篮子匹配查询的实际嵌套子文档。这不是必需的,但值得了解。

用例 2:

GET baskets/_search
{
  "sort": [
    {
      "fruit.number": {
        "nested_path": "fruit",
        "order": "desc"
      }
    }
  ]
}

用例 2 编辑:

可能有更干净的方法可以做到这一点,但我会采用以下方法:

GET baskets/_search
{
  "size": 0,
  "aggs": {
    "multiply_and_add": {
      "scripted_metric": {
        "params": {
          "only_fruit_name": "apples"
        },
        "init_script": "state.by_basket_name = [:]",
        "map_script": """
          def basket_name = params._source['name'];
          def fruits = params._source['fruit'].findAll(group -> group.name == params.only_fruit_name);
          
          for (def fruit_group : fruits) {
            def number = fruit_group.number;
            
            if (state.by_basket_name.containsKey(basket_name)) {
              state.by_basket_name[basket_name] += number;
            } else {
              state.by_basket_name[basket_name] = number;
            }
          }
        """,
        "combine_script": "return state.by_basket_name",
        "reduce_script": "return states"
      }
    }
  }
}

产生一个散列图

{
  ...
  "aggregations":{
    "multiply_and_add":{
      "value":[
        {
          "basket A":2,
          "basket B":6
        }
      ]
    }
  }
}

排序既可以在reduce_script 中完成,也可以在您的ES 响应后处理管道中完成。您当然可以选择使用(排序的)列表和lambdas...

注意所需的nested_path

【讨论】:

  • 感谢您的回答!我更新了我的问题中的数字以使其更清楚:不幸的是,对于这两个用例,数字取决于其子文档的聚合。例如,当过滤桶中大于 5 个苹果时,查询必须聚合所有子文档
  • 更新后的答案解决了用例 2。几个小时后,我找到了一个看起来有点的替代方案
  • ... 更精简。我已经用用例 2 的答案更新了我的问题。
  • 确实如此! “答案”可以在“aggregations”>“aggs_baskets”>“buckets”键中找到,在这种情况下,它包含一个“basket A”、“basket B”、...的列表,按它们的苹果数量排序准确计数。
【解决方案2】:

经过一段时间的搜索和测试,以下是(除了@joe 对用例 2 的回答之外)这两个用例的可能查询。请注意,这两个用例都需要将字段 name 的映射更改为 keyword 类型。

用例 1:哪个篮子有(严格)超过 5 个苹果?预计只有篮子 B

有关按聚合值过滤结果的更多信息,请参阅Bucket Selectors

GET /baskets/_search
{
  "aggs": {
    "aggs_baskets": {
      "terms": {
        "field": "name"
      },
      "aggs": {
        "nest":{
          "nested":{
            "path": "fruit"
          },
          "aggs":{
            "fruit_filter":{
              "filter": {
                "match": {"fruit.name": "apples"}
              },
              "aggs":{
                "fruit_sum":{
                  "sum": {"field": "fruit.number"}
                }
              }
            }
          }
        },
        "basket_sum_filter":{
          "bucket_selector":{
            "buckets_path":{
              "fruitSum":"nest > fruit_filter > fruit_sum"
            },
            "script":"params.fruitSum > 5"
          }
        }
      }
    }
  }
}

...屈服

...,

"buckets": [
    {
        "key": "basket B",
        "doc_count": 1,
        "nest": {
            "doc_count": 2,
            "fruit_filter": {
                "doc_count": 2,
                "fruit_sum": {
                    "value": 6
                }
            }
        }
    }
]

用例 2:按苹果数量对篮子进行排序。预计 篮 B 共有 6 个苹果,然后 篮 A 共有 2 个苹果。

GET /baskets/_search
{
  "aggs": {
    "aggs_baskets": {
      "terms": {
        "field": "name",
        "order": {"nest > fruit_filter > fruit_sum": "desc"}
      },
      "aggs": {
        "nest":{
          "nested":{
            "path": "fruit"
          },
          "aggs":{
            "fruit_filter":{
              "filter": {
                "term": {"fruit.name": "apple"}
              },
              "aggs":{
                "fruit_sum":{
                  "sum": {"field": "fruit.number"}
                }
              }
            }
          }
        }
      }
    }
  }
}

...屈服

...,

"buckets": [
    {
        "key": "basket B",
        "doc_count": 1,
        "nest": {
            "doc_count": 2,
            "fruit_filter": {
                "doc_count": 2,
                "fruit_sum": {
                    "value": 6
                }
            }
        }
    },
    {
        "key": "basket A",
        "doc_count": 1,
        "nest": {
            "doc_count": 2,
            "fruit_filter": {
                "doc_count": 1,
                "fruit_sum": {
                    "value": 2
                }
            }
        }
    }
]

【讨论】:

    猜你喜欢
    • 2016-04-14
    • 2018-06-13
    • 1970-01-01
    • 2020-02-18
    • 1970-01-01
    • 2022-01-14
    • 2016-11-21
    • 1970-01-01
    • 2021-11-23
    相关资源
    最近更新 更多