Można to zrobić na kilka sposobów za pomocą struktury agregacji
Tylko prosty zestaw danych, na przykład:
{
"_id" : ObjectId("538181738d6bd23253654690"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 2, "rating": 6 },
{ "_id": 3, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654691"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 4, "rating": 6 },
{ "_id": 2, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654692"),
"movies": [
{ "_id": 2, "rating": 5 },
{ "_id": 5, "rating": 6 },
{ "_id": 6, "rating": 7 }
]
}
Korzystając z pierwszego „użytkownika” jako przykładu, teraz chcesz sprawdzić, czy którykolwiek z pozostałych dwóch użytkowników ma co najmniej dwa takie same filmy.
W przypadku MongoDB 2.6 i nowszych możesz po prostu użyć $setIntersection
operator wraz z $size
operator:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document if you want to keep more than `_id`
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
}},
// Unwind the array
{ "$unwind": "$movies" },
// Build the array back with just `_id` values
{ "$group": {
"_id": "$_id",
"movies": { "$push": "$movies._id" }
}},
// Find the "set intersection" of the two arrays
{ "$project": {
"movies": {
"$size": {
"$setIntersection": [
[ 1, 2, 3 ],
"$movies"
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
Jest to nadal możliwe we wcześniejszych wersjach MongoDB, które nie mają tych operatorów, wystarczy wykonać kilka dodatkowych kroków:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document along with the "set" to match
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
"set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
}},
// Unwind both those arrays
{ "$unwind": "$movies" },
{ "$unwind": "$set" },
// Group back the count where both `_id` values are equal
{ "$group": {
"_id": "$_id",
"movies": {
"$sum": {
"$cond":[
{ "$eq": [ "$movies._id", "$set" ] },
1,
0
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
Szczegóły
To może być trochę do ogarnięcia, więc możemy przyjrzeć się każdemu etapowi i podzielić je, aby zobaczyć, co robią.
$match :Nie chcesz operować na każdym dokumencie w kolekcji, więc jest to okazja do usunięcia elementów, które prawdopodobnie nie są zgodne, nawet jeśli nadal jest więcej do zrobienia, aby znaleźć dokładne te. Tak więc oczywiste jest wykluczenie tego samego „użytkownika”, a następnie dopasowanie tylko dokumentów, które zawierają co najmniej jeden taki sam film, jaki został znaleziony dla tego „użytkownika”.
Następną rzeczą, która ma sens, jest rozważenie tego, gdy chcesz dopasować n
wpisy to tylko dokumenty, które mają tablicę "movies" większą niż n-1
może faktycznie zawierać dopasowania. Użycie $and
tutaj wygląda śmiesznie i nie jest to wymagane specjalnie, ale jeśli wymagane dopasowania to 4
wtedy ta część oświadczenia wyglądałaby tak:
"$and": [
{ "movies": { "$not": { "$size": 1 } } },
{ "movies": { "$not": { "$size": 2 } } },
{ "movies": { "$not": { "$size": 3 } } }
]
Więc zasadniczo „wykluczasz” tablice, które nie są wystarczająco długie, aby mieć n
mecze. Zauważając, że ten $size
operator w formularzu zapytania różni się od $size
dla ram agregacji. Nie można na przykład użyć tego z operatorem nierówności, takim jak $gt
jego celem jest dokładne dopasowanie żądanego „rozmiaru”. Stąd ten formularz zapytania, aby określić wszystkie możliwe rozmiary, które są mniejsze niż.
$projekt :W tym oświadczeniu jest kilka celów, z których niektóre różnią się w zależności od posiadanej wersji MongoDB. Po pierwsze i opcjonalnie kopia dokumentu jest przechowywana pod _id
wartość, aby te pola nie były modyfikowane przez pozostałe kroki. Druga część to zachowanie tablicy „movies” u góry dokumentu jako kopii do następnego etapu.
To, co dzieje się również w wersji przedstawionej dla wersji wcześniejszych niż 2.6, to dodatkowa tablica reprezentująca _id
wartości dla „filmów” do dopasowania. Użycie $cond
operator tutaj jest po prostu sposobem na stworzenie „dosłownej” reprezentacji tablicy. Co zabawne, MongoDB 2.6 wprowadza operator znany jako $literal
aby zrobić dokładnie to bez zabawnego sposobu, w jaki używamy $cond
właśnie tutaj.
$odpręż się :Aby zrobić cokolwiek dalej, tablica movies musi zostać rozwinięta, ponieważ w obu przypadkach jest to jedyny sposób na odizolowanie istniejącego _id
wartości dla wpisów, które muszą być dopasowane do „zestawu”. Tak więc w wersji wcześniejszej niż 2.6 musisz "rozwinąć" obie obecne tablice.
$grupa :W przypadku MongoDB 2.6 i nowszych po prostu grupujesz z powrotem do tablicy, która zawiera tylko _id
wartości filmów z usuniętymi „ocenami”.
Przed wersją 2.6, ponieważ wszystkie wartości są prezentowane „obok siebie” (z dużą ilością duplikatów), porównujesz te dwie wartości, aby sprawdzić, czy są takie same. Gdzie to jest true
, to mówi $cond
instrukcja operatora, aby zwrócić wartość 1
lub 0
gdzie warunek to false
. To jest bezpośrednio przekazywane z powrotem przez $sum
aby zsumować liczbę pasujących elementów w tablicy do wymaganego „zestawu”.
$projekt :Inną częścią MongoDB 2.6 i nowszych jest to, że odesłałeś tablicę "filmów" _id
wartości, których następnie używasz $setIntersection
aby bezpośrednio porównać te tablice. Wynikiem tego jest tablica zawierająca te same elementy, która jest następnie pakowana w $size
operatora, aby określić, ile elementów zostało zwróconych w tym pasującym zestawie.
$match :Jest to ostatni etap, który został tutaj zaimplementowany, który polega na dopasowywaniu tylko tych dokumentów, których liczba przecinających się elementów była większa lub równa wymaganej liczbie.
Końcowy
W zasadzie tak to robisz. Wcześniejsze niż 2.6 są nieco bardziej zawiłe i wymagają nieco więcej pamięci ze względu na rozszerzenie, które odbywa się poprzez zduplikowanie każdego elementu tablicy, który znajduje się we wszystkich możliwych wartościach zestawu, ale nadal jest to poprawny sposób na zrobienie tego.
Wszystko, co musisz zrobić, to zastosować to z większym n
pasujące wartości, aby spełnić Twoje warunki i oczywiście upewnij się, że oryginalne dopasowanie użytkownika ma wymagany n
możliwości. W przeciwnym razie po prostu wygeneruj to na n-1
z długości tablicy „filmów” użytkownika.