Geralmente, o que você está descrevendo é uma pergunta relativamente comum na comunidade MongoDB, que poderíamos descrever como a "principal
n
problemas de resultados". Isto é quando é dada alguma entrada que provavelmente é classificada de alguma forma, como obter o n
principal resultados sem depender de valores de índice arbitrários nos dados. O MongoDB tem o
$first
operador que está disponível para a estrutura de agregação
que lida com a parte "top 1" do problema, pois isso realmente leva o "primeiro" item encontrado em um limite de agrupamento, como seu "tipo". Mas obter mais de "um" resultado, é claro, envolve um pouco mais. Existem alguns problemas do JIRA sobre como modificar outros operadores para lidar com n
resultados ou "restringir" ou "fatiar". Notavelmente SERVER-6074
. Mas o problema pode ser tratado de algumas maneiras. As implementações populares do padrão Rails Active Record para armazenamento MongoDB são Mongoid e Mongo Mapper , ambos permitem o acesso às funções de coleção "nativas" do mongodb por meio de um
.collection
acessador. Isso é o que você basicamente precisa para poder usar métodos nativos, como .agregar()
que suporta mais funcionalidades do que a agregação geral do Active Record. Aqui está uma abordagem de agregação com mongoid, embora o código geral não seja alterado quando você tiver acesso ao objeto de coleção nativo:
require "mongoid"
require "pp";
Mongoid.configure.connect_to("test");
class Item
include Mongoid::Document
store_in collection: "item"
field :type, type: String
field :pos, type: String
end
Item.collection.drop
Item.collection.insert( :type => "A", :pos => "First" )
Item.collection.insert( :type => "A", :pos => "Second" )
Item.collection.insert( :type => "A", :pos => "Third" )
Item.collection.insert( :type => "A", :pos => "Forth" )
Item.collection.insert( :type => "B", :pos => "First" )
Item.collection.insert( :type => "B", :pos => "Second" )
Item.collection.insert( :type => "B", :pos => "Third" )
Item.collection.insert( :type => "B", :pos => "Forth" )
res = Item.collection.aggregate([
{ "$group" => {
"_id" => "$type",
"docs" => {
"$push" => {
"pos" => "$pos", "type" => "$type"
}
},
"one" => {
"$first" => {
"pos" => "$pos", "type" => "$type"
}
}
}},
{ "$unwind" => "$docs" },
{ "$project" => {
"docs" => {
"pos" => "$docs.pos",
"type" => "$docs.type",
"seen" => {
"$eq" => [ "$one", "$docs" ]
},
},
"one" => 1
}},
{ "$match" => {
"docs.seen" => false
}},
{ "$group" => {
"_id" => "$_id",
"one" => { "$first" => "$one" },
"two" => {
"$first" => {
"pos" => "$docs.pos",
"type" => "$docs.type"
}
},
"splitter" => {
"$first" => {
"$literal" => ["one","two"]
}
}
}},
{ "$unwind" => "$splitter" },
{ "$project" => {
"_id" => 0,
"type" => {
"$cond" => [
{ "$eq" => [ "$splitter", "one" ] },
"$one.type",
"$two.type"
]
},
"pos" => {
"$cond" => [
{ "$eq" => [ "$splitter", "one" ] },
"$one.pos",
"$two.pos"
]
}
}}
])
pp res
A nomenclatura nos documentos na verdade não é usada pelo código, e os títulos nos dados mostrados para "Primeiro", "Segundo" etc. um resultado.
Portanto, a abordagem aqui é essencialmente criar uma "pilha" de documentos "agrupados" por sua chave, como "tipo". A primeira coisa aqui é pegar o "primeiro" documento dessa pilha usando o
$first
operador. As etapas subsequentes correspondem aos elementos "vistos" da pilha e os filtram, então você retira o documento "próximo" da pilha novamente usando o
$first
operador. As etapas finais são realmente apenas para retornar os documentos ao formato original, conforme encontrado na entrada, que geralmente é o que se espera de uma consulta desse tipo. Portanto, o resultado é, obviamente, apenas os 2 principais documentos para cada tipo:
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }
Houve uma discussão e versão mais longa disso, bem como outras soluções nesta resposta recente:
Agregação Mongodb $grupo, tamanho restrito da matriz
Essencialmente a mesma coisa, apesar do título e desse caso, estava procurando corresponder até 10 entradas principais ou mais. Há algum código de geração de pipeline também para lidar com correspondências maiores, bem como algumas abordagens alternativas que podem ser consideradas dependendo de seus dados.