Elasticsearch分页排序聚合结果。

9
据我所知,在Elasticsearch中没有像以下这样做的方法:
SELECT * FROM myindex
GROUP BY agg_field1, agg_field2, agg_field3 // aggregation
ORDER BY order_field1, order_field2, order_field3 // sort
LIMIT 1000, 5000 // paginate -- get page 6 of size 1000 records

以下是相关文档链接: 在Elasticsearch中是否有实现以上功能的方法?我们唯一的限制是记录数不会超过1000万,因此(希望)不会遇到内存错误。我考虑的方法如下:
  • 进行聚合查询
  • 获取结果数量
  • 根据所需的结果和页面大小将其分成N个段
  • 使用上述段重新运行查询
请问如何最好地完成这项任务?在您的答案/建议中,请提供有关如何在ES中执行上述SQL查询的示例代码。
作为对此问题的更新,以下是可用于测试的公共索引:
# 5.6
e=Elasticsearch('https://search-testinges-fekocjpedql2f3rneuagyukvy4.us-west-1.es.amazonaws.com')
e.search('testindex')

# 6.4 (same data as above)
e = Elasticsearch('https://search-testinges6-fycj5kjd7l5uyo6npycuashch4.us-west-1.es.amazonaws.com')
e.search('testindex6')

这个数据集有10,000条记录,可随意测试:

enter image description here

我想要进行的查询如下(sql语言):

SELECT * FROM testindex
GROUP BY store_url, status, title
ORDER BY title ASC, status DESC
LIMIT 100 OFFSET 6000

换句话说,我希望对聚合结果(包含多个聚合)进行排序,并获取偏移量。

从ES 6.1版本开始,有一个名为composite的新聚合功能,可能可以帮助您实现此目标,但由于您正在运行5.6.8版本,所以不幸的是它不可用。此外,按标题分组将不可行,因为“title”是“text”类型,而不是“keyword”类型。 - Val
此外,我不明白按照除了分组字段以外的其他字段进行排序有何意义。我的意思是,在“store_url”组中,您将拥有多个不同的“retailer_id”值,并且出现的一个可能并不代表所有相同的商店。或者我在这里漏掉了什么显而易见的东西吗? - Val
@Val -- 感谢您的反馈,我已经更新了索引,使其成为关键字类型。我会更改排序方式,使其更合理。 - user10332687
@Val 我也创建了一个新的索引,其中包含相同的数据,它是ES 6.4版本的,如果你想尝试一下的话(请参见更新后的问题)。 - user10332687
谢谢,但是testindex6中没有文档。我已经将testindex重新索引到testindex6中。 - Val
2个回答

4
复合聚合 可能会对此有所帮助,因为它允许您按多个字段分组,然后对结果进行分页。唯一不允许的是跳到给定的偏移量,但如果必要的话,您可以通过从客户端代码迭代来实现。因此,这里是一个示例查询:
POST testindex6/_search
{
  "size": 0,
  "aggs": {
    "my_buckets": {
      "composite": {
        "size": 100,
        "sources": [
          {
            "store": {
              "terms": {
                "field": "store_url"
              }
            }
          },
          {
            "status": {
              "terms": {
                "field": "status",
                "order": "desc"
              }
            }
          },
          {
            "title": {
              "terms": {
                "field": "title",
                "order": "asc"
              }
            }
          }
        ]
      },
      "aggs": {
        "hits": {
          "top_hits": {
            "size": 100
          }
        }
      }
    }
  }
}

在响应中,您将看到一个名为after_key的结构:
  "after_key": {
    "store": "http://google.com1087",
    "status": "OK1087",
    "title": "Titanic1087"
  },

这是一种光标,您需要在后续查询中使用它,例如:

{
  "size": 0,
  "aggs": {
    "my_buckets": {
      "composite": {
        "size": 100,
        "sources": [
          {
            "store": {
              "terms": {
                "field": "store_url"
              }
            }
          },
          {
            "status": {
              "terms": {
                "field": "status",
                "order": "desc"
              }
            }
          },
          {
            "title": {
              "terms": {
                "field": "title",
                "order": "asc"
              }
            }
          }
        ],
        "after": {
          "store": "http://google.com1087",
          "status": "OK1087",
          "title": "Titanic1087"
        }
      },
      "aggs": {
        "hits": {
          "top_hits": {
            "size": 100
          }
        }
      }
    }
  }
}

它将为您提供接下来的100个桶。希望这可以帮助到您。

更新:

如果您想知道总共有多少个桶,那么composite聚合将无法给出该数字。但是,由于composite聚合只是其源中所有字段的笛卡尔积,因此通过返回composite聚合中使用的每个字段的cardinalityhttps://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html)并将它们相乘,您可以得到该总数的很好近似值。

  "aggs": {
    "my_buckets": {
      "composite": {
        ...
      }
    },
    "store_cardinality": {
      "cardinality": {
        "field": "store_url"
      }
    },
    "status_cardinality": {
      "cardinality": {
        "field": "status"
      }
    },
    "title_cardinality": {
      "cardinality": {
        "field": "title"
      }
    }
  }

我们可以通过将store_cardinalitystatus_cardinalitytitle_cardinality的值相乘来得到桶的总数,或者至少是一个很好的近似值(对于高基数字段效果不佳,但对于低基数字段效果相当不错)。

使用多字段的复合聚合查询进行分页时,是否可能获取总结果计数? - Bhavesh
高基数字段会对性能产生什么影响?即使我们使用“after”标记,Elastic在幕后是否会重复操作?非常感谢! - animageofmine
问题中包含了排序部分,但是答案并没有提供解决方案,有人已经让排序正常工作了吗? - TValerii

3

字段折叠就是答案。

当我们想要按特定字段(如group by agg_field)对命中结果进行分组时,就会使用字段折叠功能。

在 Elastic 6 之前,分组字段的方法是使用聚合。但这种方法缺乏有效的分页能力。

但现在,由 Elastic 提供的字段折叠功能已经内置,非常容易实现。

下面是一个包含字段折叠的示例查询,取自上述链接。

GET /twitter/_search
{
  "query": {
      "match": {
          "message": "elasticsearch"
      }
  },
  "collapse" : {
      "field" : "user", 
      "inner_hits": {
          "name": "last_tweets", 
          "size": 5, 
          "sort": [{ "date": "asc" }] 
      },
      "max_concurrent_group_searches": 4 
  },
  "sort": ["likes"]

}


如果您想按多个字段分组怎么办?例如:GROUP BY field1,field2,field3 ...等等。ES支持深度嵌套聚合,但似乎不支持字段折叠。 - Bhavesh
是的,字段折叠不适用于此。但就其本身而言,它更好。 - Shailesh Pratapwar
@ShaileshPratapwar,您能否查看更新后的问题?这与在多个字段上进行聚合时进行排序有关。 - user10332687
@ShaileshPratapwar,我已经添加了一个测试索引和我想要使用的查询。我不认为你的答案能解决它,但请看一下! - user10332687
它对我帮助很大。谢谢。针对Elasticsearch的单个字段分组分页。 - Muthukrishna C
请注意ES端的分页限制(最多10,000个命中项)。如果您需要浏览超过10,000个命中项,应改用search_after参数。不幸的是,collapse不能与search_after一起使用。 - lu_ko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接