Elasticsearchでデータをグループ化する最良の方法を探しています。 Elasticsearchは、SQLの「group by」のようなものをサポートしていません。
1kのカテゴリと何百万もの製品があるとしましょう。完全なカテゴリツリーをレンダリングする最良の方法は何だと思いますか?もちろん、いくつかのメタデータ(アイコン、リンクターゲット、seoタイトルなど)と、カテゴリのカスタムソートが必要です。
集計の使用:例: https://found.no/play/Gist/812456 1つのフィールドでグループ化する必要があり、いくつかの追加フィールドが必要な場合に使用可能に見えます。
ファセットで複数のフィールドを使用する(機能しない):例: https://found.no/play/Gist/1aa44e2114975384a7c2 ここでは、異なるフィールド間の関係が失われています。
面白いファセットを作成する: 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つを超える投稿はできません。
集計APIでは、sub-aggregationsを使用して、複数のフィールドでグループ化できます。フィールドでグループ化したいとしますfield1
、field2
および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
一部の開発者は、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 をご覧ください。
サブアグリゲーションはあなたが必要とするものです..これはドキュメントで明示的に述べられていませんが、 structuring Aggregations によって暗黙的に見つけることができます
上位の集計の結果によってクエリがフィルター処理されたかのように、サブ集計が生成されます。これはあたかもそこで起こっているかのようです。
{
"aggregations": {
"VALUE1AGG": {
"terms": {
"field": "VALUE1",
},
"aggregations": {
"VALUE2AGG": {
"terms": {
"field": "VALUE2",
}
}
}
}
}
}