Sua melhor opção aqui é executar consultas separadas para cada "País" (de preferência em paralelo) e retornar os resultados combinados. As consultas são bastante simples, e apenas retornam os 2 principais valores após aplicar uma classificação no valor de classificação e serão executadas rapidamente, mesmo que você precise realizar várias consultas para obter o resultado completo.
A estrutura de agregação não é adequada para isso, agora e mesmo em um futuro próximo. O problema é que não existe um operador que "limite" o resultado de qualquer agrupamento de alguma forma. Então, para fazer isso, você basicamente precisa
$push
todo o conteúdo em uma matriz e extraia os valores "n superiores" disso. As operações atuais necessárias para fazer isso são bastante horríveis, e o problema principal é que os resultados provavelmente excederão o limite BSON de 16 MB por documento na maioria das fontes de dados reais.
Também há um
n
complexidade para isso devido a como você teria que fazer isso agora. Mas só para demonstrar com 2 itens:db.collection.aggregate([
// Sort content by country and rating
{ "$sort": { "Country": 1, "rating": -1 } },
// Group by country and push all items, keeping first result
{ "$group": {
"_id": "$Country",
"results": {
"$push": {
"name": "$name",
"rating": "$rating",
"id": "$id"
}
},
"first": {
"$first": {
"name": "$name",
"rating": "$rating",
"id": "$id"
}
}
}},
// Unwind the array
{ "$unwind": "results" },
// Remove the seen result from the array
{ "$redact": {
"$cond": {
"if": { "$eq": [ "$results.id", "$first.id" ] },
"then": "$$PRUNE",
"else": "$$KEEP"
}
}},
// Group to return the second result which is now first on stack
{ "$group": {
"_id": "$_id",
"first": { "$first": "$first" },
"second": {
"$first": {
"name": "$results.name",
"rating": "$results.rating",
"id": "$results.id"
}
}
}},
// Optionally put these in an array format
{ "$project": {
"results": {
"$map": {
"input": ["A","B"],
"as": "el",
"in": {
"$cond": {
"if": { "$eq": [ "$$el", "A" ] },
"then": "$first",
"else": "$second"
}
}
}
}
}}
])
Isso obtém o resultado, mas não é uma ótima abordagem e fica muito mais complexo com iterações para limites mais altos ou mesmo onde os agrupamentos têm possivelmente menos que
n
resultados para retornar em alguns casos. A série de desenvolvimento atual ( 3.1.x ) no momento da escrita tem um
$slice
operador que torna isso um pouco mais simples, mas ainda tem a mesma armadilha de "tamanho":db.collection.aggregate([
// Sort content by country and rating
{ "$sort": { "Country": 1, "rating": -1 } },
// Group by country and push all items, keeping first result
{ "$group": {
"_id": "$Country",
"results": {
"$push": {
"name": "$name",
"rating": "$rating",
"id": "$id"
}
}
}},
{ "$project": {
"results": { "$slice": [ "$results", 2 ] }
}}
])
Mas basicamente até que o framework de agregação tenha alguma forma de "limitar" o número de itens produzidos pelo
$push
ou um operador de "limite" de agrupamento semelhante, a estrutura de agregação não é realmente a solução ideal para esse tipo de problema. Consultas simples como esta:
db.collection.find({ "Country": "USA" }).sort({ "rating": -1 }).limit(1)
Executar para cada país distinto e, idealmente, em processamento paralelo por loop de evento de encadeamento com um resultado combinado produz a abordagem mais ideal no momento. Eles apenas buscam o que é necessário, que é o grande problema que a estrutura de agregação ainda não consegue lidar em tal agrupamento.
Portanto, procure suporte para fazer esses "resultados de consulta combinados" da maneira mais ideal para o idioma escolhido, pois será muito menos complexo e com muito mais desempenho do que jogar isso na estrutura de agregação.