Généralement, ce que vous décrivez est une question relativement courante dans la communauté MongoDB que nous pourrions décrire comme le "top n
problème de résultats". C'est lorsqu'on leur donne une entrée qui est probablement triée d'une manière ou d'une autre, comment obtenir le top n
résultats sans s'appuyer sur des valeurs d'index arbitraires dans les données.
MongoDB a le $first
opérateur disponible pour le framework d'agrégation
qui traite de la partie "top 1" du problème, car cela prend en fait le "premier" élément trouvé sur une limite de regroupement, comme votre "type". Mais obtenir plus d'"un" résultat est bien sûr un peu plus compliqué. Il y a quelques problèmes JIRA à ce sujet concernant la modification d'autres opérateurs pour traiter n
résultats ou "restreindre" ou "trancher". Notamment SERVER-6074
. Mais le problème peut être traité de plusieurs manières.
Les implémentations populaires du modèle Rails Active Record pour le stockage MongoDB sont Mongoid
et Mongo Mapper
, les deux permettent l'accès aux fonctions de collection mongodb "natives" via un .collection
accesseur. C'est ce dont vous avez essentiellement besoin pour pouvoir utiliser des méthodes natives telles que .aggregate()
qui prend en charge plus de fonctionnalités que l'agrégation générale des enregistrements actifs.
Voici une approche d'agrégation avec mongoid, bien que le code général ne change pas une fois que vous avez accès à l'objet de collection natif :
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
La dénomination dans les documents n'est en fait pas utilisée par le code, et les titres dans les données affichées pour "Premier", "Second", etc., sont vraiment juste là pour illustrer que vous obtenez en effet les "2 meilleurs" documents de la liste comme un résultat.
L'approche ici consiste donc essentiellement à créer une "pile" des documents "regroupés" par votre clé, telle que "type". La toute première chose ici est de prendre le "premier" document de cette pile en utilisant le $first
opérateur.
Les étapes suivantes correspondent aux éléments "vus" de la pile et les filtrent, puis vous retirez à nouveau le document "suivant" de la pile en utilisant le $first
opérateur. Les étapes finales sont vraiment justes pour remettre les documents dans le formulaire d'origine tel qu'il se trouve dans l'entrée, ce qui est généralement ce qui est attendu d'une telle requête.
Donc le résultat est bien sûr, juste les 2 premiers documents pour chaque type :
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }
Il y avait une discussion plus longue et une version de ceci ainsi que d'autres solutions dans cette réponse récente :
Agrégation Mongodb $group, restreindre la longueur du tableau
Essentiellement la même chose malgré le titre et cette affaire cherchait à faire correspondre jusqu'à 10 meilleures entrées ou plus. Il existe également un code de génération de pipeline pour traiter les correspondances plus importantes, ainsi que des approches alternatives qui peuvent être envisagées en fonction de vos données.