Elasticsearch — Pesquisa retornando agrupamento de dados
Recentemente recebi uma demanda de pesquisa no elasticsearch onde eu pesquisaria por alguns termos mas agrupasse o resultado em blocos. Elasticsearch, você pode me ajudar?
O problema consistia em pesquisar no shakspeare database por textos sobre morte dos personagem. Entretanto queremos que na resposta os filmes esteja agrupados por um outro campo, por exemplo, o speaker. Queria saber se o Romeu realmente seria quem mais falaria sobre morte.
O resultado final solicitado pela minha equipe seria algo nesse formato:
Ainda bem que não mexo em frontend. Vejam que trabalho horrível eu apresentei. Mas vamos a parte que eu curto mexer, as queries do Elasticsearch. A ideia seria agrupar pelo Speaker, ter um informativo quanto textos esse Speaker teria com esse filtro (morte) e ainda, ter a possibilidade de “espiar” alguns textos do speaker.
Bem, vamos preparar um índice (shakespeare) para realizar esses testes. O json pode ser baixado em https://download.elastic.co/demos/kibana/gettingstarted/shakespeare_6.0.json e carregado no seu elasticsearch desta forma (não iremos configurar índice algum!):
curl -H ‘Content-Type: application/x-ndjson’ -XPOST ‘localhost:9200/shakespeare/doc/_bulk?pretty’ --data-binary @shakespeare_6.0.json
Queries simples podem ser feitas para resolver o problema de pesquisar por morte, e podemos utilizar o recurso de collapse para ajudar a apresentar o resultado. Desta forma somente 1 dos registros que fazem parte do agrupamento será apresentado. Mesmo com essa opção podemos fazer o uso do sort para trocar ordenação do rankeamento para outro critério:
GET /shakespeare/_search
{
"query": {
"match": {
"text_entry": "death"
}
},
"collapse": {
"field": "speaker.keyword"
}
}
Isto resolve a questão do agrupamento , mas não resolve a questão do espiar nem permite saber quantos registros de fato fazem parte deste collapse. Para ter acesso a estas informações temos a opção de inner_hits do collapse. O inner_hits permite diversas opções, como por exemplo o source para limitar os campos retornados e o size para limitar quantos registros faram parte do inner_hits:
GET /shakespeare/_search
{
"query": {
"match": {
"text_entry": "death"
}
},
"collapse": {
"field": "speaker.keyword",
"inner_hits":{
"name": "more_speaks",
"size": 5
}
}
}
Agora estamos chegando próximo do desejado, além do hits tradicional, existe também o inner_hits onde possuímos informações do total de documentos para esse colapse, além de até 5 registros a mais. A ordenação segue a mesma ordem especificada na query principal, ou seja, o rankeamento neste caso.
Infelizmente, o collapse não permite ordenar pelo número total de itens colapsados. A única forma de descobrir o personagem que mais falou em morte é realmente usando uma agregação:
GET /shakespeare/_search
{
"size":0,
"query": {
"match": {
"text_entry": "death"
}
},
"aggs": {
"top_speaker": {
"terms": {
"field": "speaker.keyword"
},
"aggs": {
"top_speaker_hits": {
"top_hits": {
"size": 5
}
}
}
}
}
}
É Romeu, o Conde de Gloucester de Rei Lear fala tanto em mortes nas obras de Shakespeare quanto você!!!
Então temos basicamente duas técnicas para fazer o agrupamento
- uma seria utilizar o top_hits aggregation, utilizando uma agregação do tipo term que possui a vantagem de realmente ordenar pelo maior número de ocorrências, mas parece ter desvantagem de não poder ser paginados com from (apesar de existir a referencia ao from na documentação, colocar um from maior que zero ainda trouxe Gloucester como a primeira resposta).
- Para utilizarmosrecurso de paginação o collapse com inner_hits é a melhor opção.
Referências: