Има няколко начина да направите това с помощта на рамката за агрегиране
Просто прост набор от данни, например:
{
"_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 }
]
}
Използвайки първия "потребител" като пример, сега искате да откриете дали някой от другите двама потребители има поне два от едни и същи филми.
За MongoDB 2.6 и по-нови можете просто да използвате $setIntersection
оператор заедно с $size
оператор:
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 } } }
])
Това все още е възможно в по-ранни версии на MongoDB, които нямат тези оператори, само като използвате още няколко стъпки:
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 } } }
])
Подробно
Това може да е малко за разглеждане, така че можем да разгледаме всеки етап и да ги разделим, за да видим какво правят.
$match :Не искате да работите с всеки документ в колекцията, така че това е възможност да премахнете елементите, които вероятно не съвпадат, дори ако все още има още работа за намиране на точния нечий. Така че очевидните неща са да се изключи същият „потребител“ и след това да се съпоставят само документите, които имат поне един от същите филми, както е намерен за този „потребител“.
Следващото нещо, което има смисъл, е да вземете предвид това, когато искате да съпоставите n
записи, след това само документи, които имат масив "филми", който е по-голям от n-1
може наистина да съдържа съвпадения. Използването на $and
тук изглежда смешно и не се изисква специално, но ако необходимите съвпадения са 4
тогава тази действителна част от израза ще изглежда така:
"$and": [
{ "movies": { "$not": { "$size": 1 } } },
{ "movies": { "$not": { "$size": 2 } } },
{ "movies": { "$not": { "$size": 3 } } }
]
Така че вие основно „изключвате“ масиви, които вероятно не са достатъчно дълги, за да имат n
мачове. Тук отбелязваме, че този $size
операторът във формуляра за заявка е различен от $size
за рамката за агрегиране. Няма начин например да използвате това с оператор за неравенство като $gt
целта му е конкретно да съответства на искания "размер". Ето защо този формуляр за заявка за указване на всички възможни размери, които са по-малки от.
$project :Има няколко цели в това изявление, някои от които се различават в зависимост от версията на MongoDB, която имате. Първо, и по избор, копие на документ се съхранява под _id
стойност, така че тези полета да не се променят от останалите стъпки. Другата част тук е запазването на масива "movies" в горната част на документа като копие за следващия етап.
Това, което също се случва във версията, представена за версии преди 2.6, е, че има допълнителен масив, представляващ _id
стойности за съвпадение на „филмите“. Използването на $cond
тук е просто начин за създаване на "буквално" представяне на масива. Странно, но MongoDB 2.6 въвежда оператор, известен като $literal
за да направим точно това без смешния начин, по който използваме $cond
точно тук.
$unwind :За да направите нещо по-нататък, масивът от филми трябва да се развие, тъй като и в двата случая това е единственият начин да се изолира съществуващият _id
стойности за записите, които трябва да бъдат съпоставени с "набора". Така че за версията преди 2.6 трябва да "развиете" и двата налични масива.
$group :За MongoDB 2.6 и по-нови вие просто групирате обратно към масив, който съдържа само _id
стойности на филмите с премахнати "рейтинги".
Преди 2.6, тъй като всички стойности са представени "една до друга" (и с много дублиране), вие правите сравнение на двете стойности, за да видите дали са еднакви. Когато това е true
, това казва на $cond
израз на оператор за връщане на стойност 1
или 0
където условието е false
. Това се връща директно чрез $sum
за да увеличите броя на съвпадащите елементи в масива до необходимия "набор".
$project :Където това е различната част за MongoDB 2.6 и по-нова е, че тъй като сте избутали назад масив от „филми“ _id
стойности, които след това използвате $setIntersection
за директно сравняване на тези масиви. Тъй като резултатът от това е масив, съдържащ елементите, които са еднакви, той след това се обвива в $size
оператор, за да се определи колко елемента са били върнати в този съвпадащ набор.
$match :Последният етап, който е приложен тук, извършва ясна стъпка за съпоставяне само на онези документи, чийто брой пресичащи се елементи е по-голям или равен на необходимия брой.
Окончателно
Това е основно как го правите. Преди 2.6 е малко по-тромав и ще изисква малко повече памет поради разширяването, което се извършва чрез дублиране на всеки член на масива, който е намерен от всички възможни стойности на набора, но все още е валиден начин да направите това.
Всичко, което трябва да направите, е да приложите това с по-големия n
съвпадащи стойности, за да отговарят на вашите условия, и разбира се уверете се, че оригиналното съвпадение на потребителя има необходимия n
възможности. В противен случай просто генерирайте това на n-1
от дължината на "потребителския" масив от "филми".