elasticsearch 按多个字段分组

标签 elasticsearch group-by elasticsearch-query

我正在寻找在 elasticsearch 中对数据进行分组的最佳方式。 Elasticsearch 不支持 sql 中的“group by”之类的东西。

假设我有 1k 个类别和数百万种产品。您认为呈现完整类别树的最佳方式是什么?当然,您需要一些元数据(图标、链接目标、seo 标题等)和类别的自定义排序。

  1. 使用聚合: 示例:https://found.no/play/gist/8124563 如果您必须按一个字段分组并需要一些额外的字段,这看起来很有用。

  2. 在 Facet 中使用多个字段(行不通): 示例:https://found.no/play/gist/1aa44e2114975384a7c2 在这里我们失去了不同领域之间的关系。

  3. 构建有趣的方面: https://found.no/play/gist/8124810

例如,使用这 3 个“解决方案”构建类别树很糟糕。 解决方案 1 可能有效(ES 1 现在不稳定) 解决方案 2 不起作用 解决方案 3 很痛苦,因为它感觉很丑陋,您需要准备大量数据并且切面会爆炸。

也许替代方案是不在 ES 中存储任何类别数据,只存储 id https://found.no/play/gist/a53e46c91e2bf077f2e1

然后你可以从另一个系统,如redis、memcache或数据库中获取关联的类别。

这最终会产生干净的代码,但性能可能会成为一个问题。 例如,从 Memcache/Redis/数据库加载 1k 个类别可能会很慢。 另一个问题是同步 2 个数据库比同步一个数据库更难。

你是如何处理这些问题的?

很抱歉,我不能在一篇文章中发布超过 2 个链接。

最佳答案

聚合 API 允许使用子聚合 按多个字段进行分组。假设您要按字段 field1field2field3 分组:

{
  "aggs": {
    "agg1": {
      "terms": {
        "field": "field1"
      },
      "aggs": {
        "agg2": {
          "terms": {
            "field": "field2"
          },
          "aggs": {
            "agg3": {
              "terms": {
                "field": "field3"
              }
            }
          }          
        }
      }
    }
  }
}

当然,这可以根据您的需要继续进行。

更新:
为了完整起见,下面是上述查询的输出结果。下面还有用于生成聚合查询并将结果展平为字典列表的 python 代码。

{
  "aggregations": {
    "agg1": {
      "buckets": [{
        "doc_count": <count>,
        "key": <value of field1>,
        "agg2": {
          "buckets": [{
            "doc_count": <count>,
            "key": <value of field2>,
            "agg3": {
              "buckets": [{
                "doc_count": <count>,
                "key": <value of field3>
              },
              {
                "doc_count": <count>,
                "key": <value of field3>
              }, ...
              ]
            },
            {
            "doc_count": <count>,
            "key": <value of field2>,
            "agg3": {
              "buckets": [{
                "doc_count": <count>,
                "key": <value of field3>
              },
              {
                "doc_count": <count>,
                "key": <value of field3>
              }, ...
              ]
            }, ...
          ]
        },
        {
        "doc_count": <count>,
        "key": <value of field1>,
        "agg2": {
          "buckets": [{
            "doc_count": <count>,
            "key": <value of field2>,
            "agg3": {
              "buckets": [{
                "doc_count": <count>,
                "key": <value of field3>
              },
              {
                "doc_count": <count>,
                "key": <value of field3>
              }, ...
              ]
            },
            {
            "doc_count": <count>,
            "key": <value of field2>,
            "agg3": {
              "buckets": [{
                "doc_count": <count>,
                "key": <value of field3>
              },
              {
                "doc_count": <count>,
                "key": <value of field3>
              }, ...
              ]
            }, ...
          ]
        }, ...
      ]
    }
  }
}

以下 python 代码根据给定的字段列表执行分组。如果您指定 include_missing=True,它还包括缺少某些字段的值的组合(如果您拥有 Elasticsearch 的 2.0 版,则不需要它,这要归功于 this)

def group_by(es, fields, include_missing):
    current_level_terms = {'terms': {'field': fields[0]}}
    agg_spec = {fields[0]: current_level_terms}

    if include_missing:
        current_level_missing = {'missing': {'field': fields[0]}}
        agg_spec[fields[0] + '_missing'] = current_level_missing

    for field in fields[1:]:
        next_level_terms = {'terms': {'field': field}}
        current_level_terms['aggs'] = {
            field: next_level_terms,
        }

        if include_missing:
            next_level_missing = {'missing': {'field': field}}
            current_level_terms['aggs'][field + '_missing'] = next_level_missing
            current_level_missing['aggs'] = {
                field: next_level_terms,
                field + '_missing': next_level_missing,
            }
            current_level_missing = next_level_missing

        current_level_terms = next_level_terms

    agg_result = es.search(body={'aggs': agg_spec})['aggregations']
    return get_docs_from_agg_result(agg_result, fields, include_missing)


def get_docs_from_agg_result(agg_result, fields, include_missing):
    current_field = fields[0]
    buckets = agg_result[current_field]['buckets']
    if include_missing:
        buckets.append(agg_result[(current_field + '_missing')])

    if len(fields) == 1:
        return [
            {
                current_field: bucket.get('key'),
                'doc_count': bucket['doc_count'],
            }
            for bucket in buckets if bucket['doc_count'] > 0
        ]

    result = []
    for bucket in buckets:
        records = get_docs_from_agg_result(bucket, fields[1:], include_missing)
        value = bucket.get('key')
        for record in records:
            record[current_field] = value
        result.extend(records)

    return result

关于elasticsearch 按多个字段分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20775040/

相关文章:

elasticsearch - Elasticsearch是否可以按日期范围过滤而无需指定字段?

java - AWS ElasticSearch Service 版本 2.3 不适用于 Jest Client : java.net.SocketTimeoutException

python - 如何将Elasticsearch数据复制到SQL Server

MySQL 与 GROUP BY 语句发生错误(查询错误 : Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column)

sql - 如何在数学上做 "GROUP BY"?

elasticsearch - 根据ElasticSearch中的嵌套子计数对文档进行排序

elasticsearch - 运行搜寻器时收到 fatal error

json - 有什么办法可以将Logstash中的JSON数据展平

java - 将 TypedQuery 转换为具有 max、min、group by 和 order by 的 criteriaQuery (JPA 2.0)

php - 如果字母数少,为什么 Elasticsearch 不起作用?