【问题标题】:Aggregation with a term and a date range带有术语和日期范围的聚合
【发布时间】:2021-01-16 05:42:36
【问题描述】:

我是 ElasticSearch 的新手,我正在尝试进行聚合,但似乎无法正确完成。

我在 ElasticSearch 索引中有一些数据,如下所示:

{
    "customerId": "example_customer",
    "request": {
        "referer": "https://example.org",
    }
    "@timestamp": "2020-09-29T14:14:00.000Z"
}

我的映射:

{
    "mappings": {
        "properties": {
            "customerId": { "type": "keyword" },
            "request": {
                "properties": {
                    "referer": { "type": "keyword" }
                }
            }
        }
    }
}

我正在尝试获取在某个日期范围内针对特定客户出现频率最高的引荐来源网址。我可以像这样为客户制作过滤器:

var result = await _client.SearchAsync<InsightRecord>(s =>
    s.Aggregations(
        a => a
            .Filter("customer", customer =>
                customer.Filter(q => q.Term(ir => ir.CustomerId, customerId)))
            .Terms("top_referer", ts => ts.Field("request.referer"))
    )
);

return result.Aggregations.Terms("top_referer").Buckets
    .Select(bucket => new TopReferer { Url = bucket.Key, Count = bucket.DocCount ?? 0})

现在我想将其缩小到特定的时间范围。这是我目前所拥有的:

var searchDescriptor = s.Aggregations(a =>
    a.Filter("customer", customer =>
        customer.Filter(q =>
            q.Bool(b =>
                b.Must(
                    f2 => f2.DateRange(date => date.GreaterThanOrEquals(from).LessThanOrEquals(to)),
                    f1 => f1.Term(ir => ir.CustomerId, customerId)
                )
            )
        )
    )
    .Terms("top_referers", ts => ts.Field("request.referer"))
);

问题是日期过滤器没有包含在查询中,它转换为这个 JSON:

{
  "aggs": {
    "customer": {
      "filter": {
        "bool": {
          "filter": [{
              "term": {
                "customerId": {
                  "value": "example_customer"
                }
              }
            }
          ]
        }
      }
    },
    "top_referers": {
      "terms": {
        "field": "request.referer"
      }
    }
  }
}

我尝试以不同的方式订购它们,但没有帮助。在 JSON 中显示的始终是客户过滤器,并且会跳过日期范围。我还看到有些人使用结合聚合的查询,但我觉得我应该能够单独使用聚合来做到这一点。这可能吗?我在查询中做错了什么,范围没有显示在 JSON 中?

【问题讨论】:

    标签: c# json elasticsearch nest


    【解决方案1】:

    问题是日期range查询没有指定字段

    f2 => f2.DateRange(date => date.GreaterThanOrEquals(from).LessThanOrEquals(to)),
    

    向该查询添加一个字段

    f2 => f2.DateRange(date => date
        .Field("@timestamp")
        .GreaterThanOrEquals(from)
        .LessThanOrEquals(to)
    ),
    

    此外,要使过滤器聚合应用于术语聚合,术语聚合需要是过滤器聚合的sub 聚合。所以会是这样的

    var customerId = "foo";
    var from = "now-365d";
    var to = "now";
    
    var result = await _client.SearchAsync<InsightRecord>(s => s
        .Aggregations(a => a
            .Filter("customer", customer => customer
                .Filter(q => q
                    .Bool(b => b
                        .Must(
                            f2 => f2.DateRange(date => date
                                .Field("@timestamp")
                                .GreaterThanOrEquals(from)
                                .LessThanOrEquals(to)
                            ),
                            f1 => f1.Term(ir => ir.CustomerId, customerId)
                        )
                    )
                )
                .Aggregations(aa => aa
                    .Terms("top_referers", ts => ts.Field("request.referer"))
                )
            )
        )
    );
    

    虽然没有使用过滤器聚合指定日期范围和术语查询,我更倾向于将它们指定为对搜索请求的查询。计算聚合时会考虑查询。当您想查询数据集但仅在数据集的子集上运行聚合时,过滤器聚合很有用,例如如果您在所有个客户中进行搜索,但随后只想对部分客户运行聚合。在实践中,对于这个特定示例,无论查询被指定为搜索请求的查询部分,还是作为过滤器聚合,术语聚合作为 sub 聚合,结果都应该相同,但是前者可能更容易获得结果。

    指定为查询看起来像

    var result = await _client.Search<InsightRecord>(s => s
        .Query(q => q
            .Bool(b => b
                .Must(
                    f2 => f2.DateRange(date => date
                        .Field("@timestamp")
                        .GreaterThanOrEquals(from)
                        .LessThanOrEquals(to)
                    ),
                    f1 => f1.Term(ir => ir.CustomerId, customerId)
                )
            )
        )
        .Aggregations(aa => aa
            .Terms("top_referers", ts => ts.Field("request.referer"))
        )
    );
    

    此外,我们还可以做几件事

    1. 由于我们只对术语聚合的结果而不是搜索命中感兴趣,我们可以指定 .Size(0) 以不打扰在响应中返回搜索命中。
    2. bool 查询可以更简洁地表达为combining queries with operator overloading,并且由于两个查询都是谓词(文档要么匹配查询,要么不匹配),我们可以在过滤子句中指定两者以省略评分。最后的查询类似于
    var result = await _client.SearchAsync<InsightRecord>(s => s
        .Size(0)
        .Query(q => +q
            .DateRange(date => date
                .Field("@timestamp")
                .GreaterThanOrEquals(from)
                .LessThanOrEquals(to)
            ) && +q
            .Term(ir => ir.CustomerId, customerId)
        )
        .Aggregations(aa => aa
            .Terms("top_referers", ts => ts.Field("request.referer"))
        )
    );
    

    生成查询

    {
      "aggs": {
        "top_referers": {
          "terms": {
            "field": "request.referer"
          }
        }
      },
      "query": {
        "bool": {
          "filter": [{
            "range": {
              "@timestamp": {
                "gte": "now-365d",
                "lte": "now"
              }
            }
          }, {
            "term": {
              "customerId": {
                "value": "foo"
              }
            }
          }]
        }
      },
      "size": 0
    }
    

    可以按照您的问题中所述访问术语聚合存储桶。

    【讨论】:

    • 感谢您的详细解答! stackoverflow.com/a/58446776/3203104 在这里我读到将过滤器放入聚合中会比查询然后聚合更快,因为在后者中,聚合只会为我选择的那些客户运行。你能确认一下吗?
    • 您误解了链接的答案 - 与查询结合的聚合通常比具有子聚合的过滤聚合快,因为查询过滤文档和聚合然后在过滤后的文档上运行。相比之下,没有任何事先过滤的过滤聚合首先需要过滤所有文档,然后对过滤后的文档执行子聚合。在您的特定示例中,最终结果将是相同的,但 我认为 查询性能可能会更好。
    猜你喜欢
    • 2019-08-06
    • 1970-01-01
    • 2023-03-19
    • 2018-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多