Несколько групп в Elasticsearch

Мне нужно агрегировать (группировать), используя 3 поля в ES.

Могу ли я сделать это в 1 запросе или что мне нужно использовать фасет + итерацию для каждого столбца?

Спасибо

Ответ 1

Вы можете сделать это двумя способами:

1), используя несколько полей в одном результате:

пример для граней одиночных полей:

curl -X GET "http://localhost:9200/sales/order/_search?pretty=true" -d '{
  "query": {
    "query_string": {
      "query": "shohi*",
      "fields": [
        "billing_name"
      ]
    }
  },
  "facets": {
    "facet_result": {
      "terms": {
        "fields": [
          "status"
        ],
        "order": "term",
        "size": 15
      }
    }
  }
}'

пример для нескольких полей в одном результате:

curl -X GET "http://localhost:9200/sales/order/_search?pretty=true" -d '{
  "query": {
    "query_string": {
      "query": "shohi*",
      "fields": [
        "billing_name"
      ]
    }
  },
  "facets": {
    "facet_result": {
      "terms": {
        "fields": [
          "status",
          "customer_gender",
          "state"
        ],
        "order": "term",
        "size": 15
      }
    }
  }
}'

2) Используйте множественный результирующий набор:

curl -X GET "http://localhost:9200/sales/order/_search?pretty=true" -d '{
  "query": {
    "query_string": {
      "query": "*",
      "fields": [
        "increment_id"
      ]
    }
  },
  "facets": {
    "status_facets": {
      "terms": {
        "fields": [
          "status"
        ],
        "size": 50,
        "order": "term"
      }
    },
    "gender_facets": {
      "terms": {
        "fields": [
          "customer_gender"
        ]
      }
    },
    "state_facets": {
      "terms": {
        "fields": [
          "state"
        ],
        ,
        "order": "term"
      }
    }
  }
}'

Ссылка: http://www.elasticsearch.org/guide/reference/api/search/facets/terms-facet.html

Ответ 2

Начиная с версии 1.0 ElasticSearch, новый агрегированный API позволяет группировать по нескольким полям, используя подагрегированные данные. Предположим, вы хотите группировать по полям field1, field2 и field3:

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

Конечно, это может продолжаться как можно больше полей.

Update:
Для полноты, вот как выглядит результат вышеуказанного запроса. Также ниже приведен код 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, он также включает комбинации значений, в которых некоторые из полей отсутствуют (вам это не нужно, если у вас есть версия 2.0 Elasticsearch благодаря 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