MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Заявка за пресичане на вложен масив на MongoDB

Има няколко начина да направите това с помощта на рамката за агрегиране

Просто прост набор от данни, например:

{
    "_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 от дължината на "потребителския" масив от "филми".




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Диакритично търсене без значение за главни и малки букви

  2. Как да заявите документи в mongodb (pymongo), където всички ключови думи съществуват в поле?

  3. Използване на агрегиране за сортиране в сложно условно в Mongodb

  4. Node.js Mongoose.js низ към функцията ObjectId

  5. Аргументът трябва да бъде низ в nodejs