web-dev-qa-db-ja.com

elasticsearch group-by複数フィールド

Elasticsearchでデータをグループ化する最良の方法を探しています。 Elasticsearchは、SQLの「group by」のようなものをサポートしていません。

1kのカテゴリと何百万もの製品があるとしましょう。完全なカテゴリツリーをレンダリングする最良の方法は何だと思いますか?もちろん、いくつかのメタデータ(アイコン、リンクターゲット、seoタイトルなど)と、カテゴリのカスタムソートが必要です。

  1. 集計の使用:例: https://found.no/play/Gist/812456 1つのフィールドでグループ化する必要があり、いくつかの追加フィールドが必要な場合に使用可能に見えます。

  2. ファセットで複数のフィールドを使用する(機能しない):例: https://found.no/play/Gist/1aa44e2114975384a7c2 ここでは、異なるフィールド間の関係が失われています。

  3. 面白いファセットを作成する: https://found.no/play/Gist/812481

たとえば、これらの3つの「ソリューション」を使用してカテゴリツリーを作成するのは大変です。ソリューション1は機能する可能性があります(現在、ES 1は安定していません)ソリューション2が機能しませんソリューション3見苦しいので苦痛です。大量のデータを準備する必要があり、ファセットが爆発します。

たぶん代替案は、ESにカテゴリデータを格納せず、IDだけを保存することです https://found.no/play/Gist/a53e46c91e2bf077f2e1

次に、redis、memcache、データベースなど、別のシステムから関連カテゴリを取得できます。

これは最終的にクリーンなコードになりますが、パフォーマンスが問題になる可能性があります。たとえば、Memcache/Redis /データベースからの1kカテゴリの読み込みは遅くなる可能性があります。別の問題は、2つのデータベースの同期が1つのデータベースの同期よりも難しいことです。

そのような問題にどのように対処しますか?

リンクは申し訳ありませんが、1つの記事に2つを超える投稿はできません。

13
timg

集計APIでは、sub-aggregationsを使用して、複数のフィールドでグループ化できます。フィールドでグループ化したいとしますfield1field2およびfield3

{
  "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コードは、フィールドのリストを指定してgroup-byを実行します。指定したのは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
25
Joe

一部の開発者は、Spring DATA ESとJava ES API。

見つけてください:-

List<FieldObject> fieldObjectList = Lists.newArrayList();
    SearchQuery aSearchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withIndices(indexName).withTypes(type)
            .addAggregation(
                    terms("ByField1").field("field1").subAggregation(AggregationBuilders.terms("ByField2").field("field2")
                            .subAggregation(AggregationBuilders.terms("ByField3").field("field3")))
                    )
            .build();
    Aggregations aField1Aggregations = elasticsearchTemplate.query(aSearchQuery, new ResultsExtractor<Aggregations>() {
        @Override
        public Aggregations extract(SearchResponse aResponse) {
            return aResponse.getAggregations();
        }
    });
    Terms aField1Terms = aField1Aggregations.get("ByField1");
    aField1Terms.getBuckets().stream().forEach(aField1Bucket -> {
        String field1Value = aField1Bucket.getKey();
        Terms aField2Terms = aField1Bucket.getAggregations().get("ByField2");

        aField2Terms.getBuckets().stream().forEach(aField2Bucket -> {
            String field2Value = aField2Bucket.getKey();
            Terms aField3Terms = aField2Bucket.getAggregations().get("ByField3");

            aField3Terms.getBuckets().stream().forEach(aField3Bucket -> {
                String field3Value = aField3Bucket.getKey();
                Long count = aField3Bucket.getDocCount();

                FieldObject fieldObject = new FieldObject();
                fieldObject.setField1(field1Value);
                fieldObject.setField2(field2Value);
                fieldObject.setField3(field3Value);
                fieldObject.setCount(count);
                fieldObjectList.add(fieldObject);
            });
        });
    });

インポートは同じように行う必要があります:-

import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.terms; 
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.TermFilterBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsExtractor;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;

Composite Aggregationクエリを次のように使用できます。このタイプのクエリは、バケット数がESの通常の値を超えた場合にも結果にページ番号を付けます。 「後」フィールドを使用することで、残りのバケットにアクセスできます。

"aggs": {
    "my_buckets": {
      "composite": {
        "sources": [
          {
            "field1": {
              "terms": {
                "field": "field1"
              }
            }
          },
          {
            "field2": {
              "terms": {
                "field": "field2"
              }
            }
          },
         {
            "field3": {
              "terms": {
                "field": "field3"
              }
            }
          },
        ]
      }
    }
  }

詳細については、ESページ bucket-composite-aggregation をご覧ください。

2
Ali Mirzaei

サブアグリゲーションはあなたが必要とするものです..これはドキュメントで明示的に述べられていませんが、 structuring Aggregations によって暗黙的に見つけることができます

上位の集計の結果によってクエリがフィルター処理されたかのように、サブ集計が生成されます。これはあたかもそこで起こっているかのようです。

{
"aggregations": {
    "VALUE1AGG": {
      "terms": {
        "field": "VALUE1",
      },
      "aggregations": {
        "VALUE2AGG": {
           "terms": {
             "field": "VALUE2",
          }
        }
      }
    }
  }
}
1
Summer-Sky