In genere ciò che stai descrivendo è una domanda relativamente comune nella comunità di MongoDB che potremmo descrivere come la "top n
problema con i risultati". Questo è quando viene fornito un input che è probabilmente ordinato in qualche modo, come ottenere il primo n
risultati senza fare affidamento su valori di indice arbitrari nei dati.
MongoDB ha il $first
operatore disponibile per il framework di aggregazione
che si occupa della parte "top 1" del problema, poiché in realtà prende il "primo" elemento trovato su un limite di raggruppamento, come il tuo "tipo". Ma ottenere più di "un" risultato ovviamente diventa un po' più complicato. Ci sono alcuni problemi JIRA su questo riguardo alla modifica di altri operatori per gestire n
risultati o "restrict" o "slice". In particolare SERVER-6074
. Ma il problema può essere gestito in diversi modi.
Le implementazioni popolari del modello Rails Active Record per l'archiviazione MongoDB sono Mongoid
e Mongo Mapper
, entrambi consentono l'accesso alle funzioni di raccolta mongodb "native" tramite un .collection
accessorio. Questo è ciò di cui hai praticamente bisogno per essere in grado di utilizzare metodi nativi come .aggregato()
che supporta più funzionalità rispetto all'aggregazione generale di Active Record.
Ecco un approccio di aggregazione con mongoid, anche se il codice generale non cambia una volta che hai accesso all'oggetto raccolta 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
La denominazione nei documenti in realtà non è utilizzata dal codice e i titoli nei dati mostrati per "Primo", "Secondo" ecc., sono davvero lì solo per illustrare che stai effettivamente ottenendo i "primi 2" documenti dall'elenco come un risultato.
Quindi l'approccio qui è essenzialmente quello di creare una "pila" di documenti "raggruppati" dalla tua chiave, come "tipo". La prima cosa qui è prendere il "primo" documento da quello stack usando $first
operatore.
I passaggi successivi corrispondono agli elementi "visti" dallo stack e li filtrano, quindi si rimuove nuovamente il documento "successivo" dallo stack utilizzando $first
operatore. I passaggi finali sono davvero solo x restituire i documenti alla forma originale trovata nell'input, che è generalmente ciò che ci si aspetta da una tale query.
Quindi il risultato sono ovviamente solo i primi 2 documenti per ogni tipo:
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }
C'è stata una discussione e una versione più lunghe di questo, così come altre soluzioni in questa risposta recente:
Aggregazione Mongodb $group, limita la lunghezza dell'array
Essenzialmente la stessa cosa nonostante il titolo e quel caso cercassero di abbinare fino a 10 voci principali o più. C'è anche del codice di generazione della pipeline per gestire corrispondenze più grandi, nonché alcuni approcci alternativi che possono essere presi in considerazione a seconda dei tuoi dati.