group-by elasticsearch faceted-search facet

group by - Grupo ElasticSearch por mĂșltiples campos



group-by faceted-search (2)

Lo único que encontré fue: varios grupos por Elasticsearch

Básicamente, estoy tratando de obtener el equivalente de ES de la siguiente consulta de MySQL:

select gender, age_range, count(distinct profile_id) as count FROM TABLE group by age_range, gender

La edad y el género en sí mismos eran fáciles de obtener:

{ "query": { "match_all": {} }, "facets": { "ages": { "terms": { "field": "age_range", "size": 20 } }, "gender_by_age": { "terms": { "fields": [ "age_range", "gender" ] } } }, "size": 0 }

lo que da:

{ "ages": { "_type": "terms", "missing": 0, "total": 193961, "other": 0, "terms": [ { "term": 0, "count": 162643 }, { "term": 3, "count": 10683 }, { "term": 4, "count": 8931 }, { "term": 5, "count": 4690 }, { "term": 6, "count": 3647 }, { "term": 2, "count": 3247 }, { "term": 1, "count": 120 } ] }, "total_gender": { "_type": "terms", "missing": 0, "total": 193961, "other": 0, "terms": [ { "term": 1, "count": 94799 }, { "term": 2, "count": 62645 }, { "term": 0, "count": 36517 } ] } }

Pero ahora necesito algo que se vea así:

[breakdown_gender] => Array ( [1] => Array ( [0] => 264 [1] => 1 [2] => 6 [3] => 67 [4] => 72 [5] => 40 [6] => 23 ) [2] => Array ( [0] => 153 [2] => 2 [3] => 21 [4] => 35 [5] => 22 [6] => 11 ) )

Tenga en cuenta que 0,1,2,3,4,5,6 son "mapeos" para los rangos de edad, por lo que en realidad significan algo :) y no solo números. por ejemplo, Sexo [1] (que es "masculino") se divide en el rango de edad [0] (que es "menor de 18") con un recuento de 246.


A partir de la versión 1.0 de ElasticSearch , la nueva API de agregaciones permite la agrupación por múltiples campos, utilizando sub-agregaciones . Supongamos que desea agrupar por los campos field2 , field2 y field2 :

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

Por supuesto, esto puede continuar para tantos campos como desee.

Actualizar:
Para completar, así es como se ve el resultado de la consulta anterior. También debajo está el código python para generar la consulta de agregación y aplanar el resultado en una lista de diccionarios.

{ "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> }, ... ] }, ... ] }, ... ] } } }

El siguiente código python realiza el grupo-por la lista dada de campos. Especifico include_missing=True , también incluye combinaciones de valores donde faltan algunos de los campos (no lo necesita si tiene la versión 2.0 de Elasticsearch gracias a 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


Como solo tiene 2 campos, una forma simple es hacer dos consultas con facetas únicas. Para hombre:

{ "query" : { "term" : { "gender" : "Male" } }, "facets" : { "age_range" : { "terms" : { "field" : "age_range" } } } }

Y para mujer:

{ "query" : { "term" : { "gender" : "Female" } }, "facets" : { "age_range" : { "terms" : { "field" : "age_range" } } } }

O puede hacerlo en una sola consulta con un filtro de facetas (consulte este enlace para obtener más información)

{ "query" : { "match_all": {} }, "facets" : { "age_range_male" : { "terms" : { "field" : "age_range" }, "facet_filter":{ "term": { "gender": "Male" } } }, "age_range_female" : { "terms" : { "field" : "age_range" }, "facet_filter":{ "term": { "gender": "Female" } } } } }

Actualizar:

Como las facetas están a punto de ser eliminadas. Esta es la solución con agregaciones:

{ "query": { "match_all": {} }, "aggs": { "male": { "filter": { "term": { "gender": "Male" } }, "aggs": { "age_range": { "terms": { "field": "age_range" } } } }, "female": { "filter": { "term": { "gender": "Female" } }, "aggs": { "age_range": { "terms": { "field": "age_range" } } } } } }