Generelt er det, du beskriver, et relativt almindeligt spørgsmål omkring MongoDB-fællesskabet, som vi kunne beskrive som "top n
resultater problem". Dette er, når der gives noget input, der sandsynligvis er sorteret på en eller anden måde, hvordan man får den øverste n
resultater uden at stole på vilkårlige indeksværdier i dataene.
MongoDB har $first
operatør, som er tilgængelig for aggregationsramme
som omhandler "top 1"-delen af problemet, da dette faktisk tager det "første" element fundet på en grupperingsgrænse, såsom din "type". Men at få mere end "et" resultat bliver selvfølgelig lidt mere involveret. Der er nogle JIRA-problemer om dette om at ændre andre operatører til at håndtere n
resultater eller "begræns" eller "slice". Især SERVER-6074
. Men problemet kan håndteres på flere måder.
Populære implementeringer af skinnernes Active Record-mønster til MongoDB-lagring er Mongoid
og Mongo Mapper
, giver begge adgang til de "native" mongodb-samlingsfunktioner via en .collection
tilbehør. Dette er, hvad du grundlæggende har brug for for at kunne bruge native metoder såsom .aggregate()
som understøtter mere funktionalitet end generel Active Record-aggregering.
Her er en aggregeringstilgang med mongoid, selvom den generelle kode ikke ændres, når du først har adgang til det oprindelige samlingsobjekt:
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
Navngivningen i dokumenterne bruges faktisk ikke af koden, og titler i dataene vist for "First", "Second" osv. er egentlig kun til for at illustrere, at du faktisk får "top 2" dokumenterne fra listen som et resultat.
Så fremgangsmåden her er i det væsentlige at skabe en "stak" af dokumenterne "grupperet" efter din nøgle, såsom "type". Den allerførste ting her er at tage det "første" dokument fra den stak ved hjælp af $first
operatør.
De efterfølgende trin matcher de "sete" elementer fra stakken og filtrerer dem, så tager du det "næste" dokument ud af stakken igen ved hjælp af $first
operatør. De sidste trin derinde er egentlig bare at returnere dokumenterne til den originale form, som findes i inputtet, hvilket generelt er, hvad der forventes af en sådan forespørgsel.
Så resultatet er selvfølgelig kun de øverste 2 dokumenter for hver type:
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }
Der var en længere diskussion og version af dette samt andre løsninger i dette seneste svar:
Mongodb aggregering $group, begræns længden af array
Grundlæggende det samme på trods af titlen, og den sag søgte at matche op til 10 topposter eller flere. Der er også noget pipeline-genereringskode til håndtering af større matches samt nogle alternative tilgange, der kan overvejes afhængigt af dine data.