Ogólnie rzecz biorąc to, co opisujesz, jest stosunkowo powszechnym pytaniem w społeczności MongoDB, które możemy opisać jako "najlepsze n
problem z wynikami". To jest, gdy podano dane wejściowe, które prawdopodobnie są w jakiś sposób posortowane, jak uzyskać najlepsze n
wyniki bez polegania na arbitralnych wartościach indeksu w danych.
MongoDB ma $first
operator dostępny dla struktury agregacji
który zajmuje się „największą 1” częścią problemu, ponieważ w rzeczywistości zajmuje „pierwszy” element znaleziony na granicy grupowania, taki jak „typ”. Ale uzyskanie więcej niż „jednego” wyniku jest oczywiście trochę bardziej zaangażowane. Istnieje kilka problemów z JIRA dotyczących modyfikowania innych operatorów w celu obsługi n
wyniki lub „ogranicz” lub „wycinek”. W szczególności SERVER-6074
. Problem można jednak rozwiązać na kilka sposobów.
Popularne implementacje wzorca szyny Active Record do przechowywania danych MongoDB to Mongoid
i Mongo Mapper
, oba umożliwiają dostęp do "natywnych" funkcji kolekcji mongodb poprzez .collection
akcesor. Właśnie tego potrzebujesz, aby móc korzystać z natywnych metod, takich jak .aggregate()
który obsługuje więcej funkcji niż ogólna agregacja Active Record.
Oto podejście agregacyjne z mongoid, chociaż ogólny kod nie zmienia się, gdy masz dostęp do natywnego obiektu kolekcji:
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
Nazewnictwo w dokumentach w rzeczywistości nie jest używane przez kod, a tytuły w danych pokazanych dla „Pierwszy”, „Drugi” itp. są tak naprawdę tylko po to, aby zilustrować, że rzeczywiście otrzymujesz „najlepsze 2” dokumenty z listy jako wynik.
Tak więc podejście tutaj polega zasadniczo na stworzeniu „stosu” dokumentów „pogrupowanych” według klucza, takiego jak „typ”. Pierwszą rzeczą tutaj jest pobranie "pierwszego" dokumentu z tego stosu za pomocą $first
operatora.
Kolejne kroki dopasowują „widziane” elementy ze stosu i filtrują je, a następnie ponownie usuwasz „następny” dokument ze stosu, używając $first
operator. Ostatnie kroki są tak naprawdę po prostu przywrócenie dokumentów do oryginalnej postaci, jaka została znaleziona w danych wejściowych, czego generalnie oczekuje się od takiego zapytania.
Wynik to oczywiście tylko 2 najlepsze dokumenty dla każdego typu:
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }
W tej ostatniej odpowiedzi była dłuższa dyskusja i wersja tego, a także innych rozwiązań:
Agregacja Mongodb $grupa, ogranicz długość tablicy
Zasadniczo to samo, pomimo tytułu, a ta sprawa szukała dopasowania do 10 najlepszych wpisów lub więcej. Jest tam również trochę kodu generowania potoku do radzenia sobie z większymi dopasowaniami, a także kilka alternatywnych podejść, które można rozważyć w zależności od danych.