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"
}
}
}
}
}
}