在Elasticsearch中按嵌套字段值的总和查询文档

时间:2018-12-22 21:09:32

标签: elasticsearch

ElasticSearch的排名初学者。

我有一个客户列表,他们的订单是一个嵌套字段。假设文档结构如下:

[
  { customerId: 123,
    birthday: 1980-01-01,
    orders: [
      {
        orderValue: 1500,
        orderDate: 2018-12-18T12:18:12Z
      },
      [...]
    ]
  },
  [...]
}

我要查询的是:从两个日期之间订购了一定数量的用户的列表。而且我希望能够将其与范围查询结合起来,例如生日。

我已经到了可以使用聚合获得每个订户两个日期之间的有序总和的地方:

{
  "size": 0,
  "aggs": {
    "foo": {
      "nested": {
        "path": "orders"
      },
      "aggs": {
        "grouped_by_customerId": {
          "terms": {
            "field": "orders.customerId.keyword"
          },
          "aggs": {
            "filtered_by_date": {
              "filter": {
                "range": {
                  "orders.orderDate": {
                    "from": "2018-01-28",
                    "to": null,
                    "include_lower": false,
                    "include_upper": true,
                    "format": "yyyy-MM-dd",
                    "boost": 1
                  }
                }
              },
              "aggs": {
                "sum": {
                  "sum": {
                    "field": "orders.orderValue"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

但是,我想限制我在“查询”部分返回的结果,以便与我们所有其他过滤器更好地混合。

我的第一个想法是拥有一个脚本过滤器,并将边界日期和最小值作为参数传递,但是随后我不得不遍历文档的嵌套文档,这似乎行不通。

最后一个想法可能吗?如果可以,怎么办?

谢谢!

1 个答案:

答案 0 :(得分:0)

最后使用Function Score query自己解决了这个问题,

{
  "query": {
    "bool": {
      "must": [
        {
          "function_score": {
            "min_score": 1,
            "query": {
              "nested": {
                "path": "orders",
                "ignore_unmapped": false,
                "score_mode": "min",
                "boost": 1,
                "query": {
                  "range": {
                    "orders.orderDate": {
                      "from": "2018-12-10",
                      "to": null,
                      "include_lower": true,
                      "include_upper": true,
                      "format": "yyyy-MM-dd",
                      "boost": 1
                    }
                  }
                }
              }
            },
            "functions": [
              {
                "filter": {
                  "match_all": {}
                },
                "script_score": {
                  "script": {
                    "source": "ArrayList x = params['_source']['orders'];if (x == null) { return 0 }long result = x.stream().filter(order -> {  if(params.startDate != null && !ZonedDateTime.parse(order.orderDate).isAfter(ZonedDateTime.parse(params.startDate))) return false; return true}).mapToLong(order->Long.parseLong(order.orderValue)).sum();if(params.operator == 'GT') return result > params.totalOrderValue ? 2 : 0;else if (params.operator == 'GE') return result >= params.totalOrderValue ? 3 : 0;else if (params.operator == 'LE') return result <= params.totalOrderValue ? 4 : 0;else if(params.operator == 'LT') return result < params.totalOrderValue ? 5 : 0;return result == params.totalOrderValue ? 6 : 0",
                    "lang": "painless",
                    "params": {
                      "totalOrderValue": 120,
                      "operator": "GE",
                      "startDate": "2012-12-10T23:00:00.000Z"
                    }
                  }
                }
              }
            ],
            "score_mode": "multiply",
            "max_boost": 3.4028235e+38,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  }
}

这产生的实际分数是调试输出以测试操作员,但是min_score为1意味着它们中的任何一个都匹配。使用_source非常慢。

在function_score中没有查询的情况下,它可以工作,但是要花300秒左右才能遍历300万条记录。通过查询,您只会查看订单与日期范围实际匹配的客户。

由于无痛脚本处理了整个订单列表,因此必须重做日期数学。为此可以进行一些优化,但至少我有概念证明。

我以前看过这个问题,但没有满意的答案,所以希望有人觉得这很有用。