Im Allgemeinen ist das, was Sie beschreiben, eine relativ häufige Frage in der MongoDB-Community, die wir als „top n
“ bezeichnen könnten Ergebnisproblem". Dies ist der Fall, wenn eine Eingabe gegeben wird, die wahrscheinlich auf irgendeine Weise sortiert ist, wie man die obersten n
erhält Ergebnisse, ohne sich auf willkürliche Indexwerte in den Daten zu verlassen.
MongoDB hat den $first
-Operator, der für das Aggregation Framework
verfügbar ist die sich mit dem "obersten 1"-Teil des Problems befasst, da dies tatsächlich das "erste" Element nimmt, das an einer Gruppierungsgrenze gefunden wird, wie z. B. Ihr "Typ". Aber mehr als "ein" Ergebnis zu bekommen, ist natürlich ein wenig komplizierter. Es gibt einige JIRA-Probleme in Bezug auf die Änderung anderer Operatoren, um mit n
umzugehen Ergebnisse oder "restrict" oder "slice". Insbesondere SERVER-6074
. Aber das Problem kann auf verschiedene Weise gelöst werden.
Beliebte Implementierungen des Rails-Active-Record-Musters für MongoDB-Speicher sind Mongoid
und Mongo Mapper
, erlauben beide den Zugriff auf die "nativen" mongodb-Erfassungsfunktionen über eine .collection
Accessor. Das brauchen Sie grundsätzlich, um native Methoden wie .aggregate()
die mehr Funktionalität als die allgemeine Active Record-Aggregation unterstützt.
Hier ist ein Aggregationsansatz mit mongoid, obwohl sich der allgemeine Code nicht ändert, sobald Sie Zugriff auf das native Sammlungsobjekt haben:
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
Die Benennung in den Dokumenten wird tatsächlich nicht vom Code verwendet, und Titel in den Daten, die für „Erster“, „Zweiter“ usw. angezeigt werden, sind wirklich nur dazu da, um zu veranschaulichen, dass Sie tatsächlich die „Top 2“-Dokumente aus der Auflistung als erhalten ein Ergebnis.
Der Ansatz hier besteht also im Wesentlichen darin, einen "Stapel" der Dokumente zu erstellen, die nach Ihrem Schlüssel "gruppiert" sind, z. B. "Typ". Das allererste, was hier zu tun ist, ist, das „erste“ Dokument von diesem Stapel zu nehmen, indem der $first
Betreiber.
Die nachfolgenden Schritte matchen die „gesehenen“ Elemente aus dem Stack und filtern sie, dann nimmst du das „nächste“ Dokument wieder vom Stack mit Hilfe des $first
Operator. Die letzten Schritte darin bestehen wirklich nur darin, die Dokumente in die ursprüngliche Form zurückzubringen, wie sie in der Eingabe gefunden wurde, was im Allgemeinen von einer solchen Abfrage erwartet wird.
Das Ergebnis sind also natürlich nur die Top 2 Dokumente für jeden Typ:
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }
Es gab eine längere Diskussion und Version davon sowie andere Lösungen in dieser letzten Antwort:
Mongodb-Aggregation $group, Länge des Arrays beschränken
Trotz des Titels im Wesentlichen dasselbe, und dieser Fall wollte bis zu 10 Top-Einträge oder mehr zusammenbringen. Es gibt dort auch etwas Pipeline-Generierungscode für den Umgang mit größeren Übereinstimmungen sowie einige alternative Ansätze, die je nach Ihren Daten in Betracht gezogen werden können.