sql >> Database >  >> NoSQL >> MongoDB

MongoDB Geneste Array Intersection Query

Er zijn een aantal manieren om dit te doen met behulp van het aggregatieraamwerk

Gewoon een simpele set gegevens, bijvoorbeeld:

{
    "_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 }
    ]
}

Als u de eerste "gebruiker" als voorbeeld gebruikt, wilt u nu zien of een van de andere twee gebruikers ten minste twee dezelfde films heeft.

Voor MongoDB 2.6 en hoger kunt u eenvoudig de $setIntersection operator samen met de $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 } } }

])

Dit is nog steeds mogelijk in eerdere versies van MongoDB die deze operators niet hebben, met slechts een paar extra stappen:

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 } } }
])

In detail

Dat is misschien een beetje om in je op te nemen, dus we kunnen elke fase bekijken en die opsplitsen om te zien wat ze aan het doen zijn.

$match :u wilt niet met elk document in de verzameling werken, dus dit is een kans om de items te verwijderen die mogelijk niet overeenkomen, zelfs als er nog meer werk te doen is om de exacte te vinden degenen. Het ligt dus voor de hand om dezelfde "gebruiker" uit te sluiten en dan alleen de documenten te matchen die ten minste één van dezelfde films bevatten als die voor die "gebruiker".

Het volgende dat logisch is, is om te overwegen dat wanneer u n . wilt matchen vermeldingen dan alleen documenten die een "films"-array hebben die groter is dan n-1 kan mogelijk daadwerkelijk overeenkomsten bevatten. Het gebruik van $and hier ziet er grappig uit en is niet specifiek vereist, maar als de vereiste overeenkomsten 4 . waren dan zou dat eigenlijke deel van de verklaring er als volgt uitzien:

        "$and": [
            { "movies": { "$not": { "$size": 1 } } },
            { "movies": { "$not": { "$size": 2 } } },
            { "movies": { "$not": { "$size": 3 } } }
        ]

U sluit dus in feite arrays uit die mogelijk niet lang genoeg zijn om n . te hebben wedstrijden. Hierbij opmerkend dat deze $size operator in het queryformulier is anders dan $size voor het aggregatiekader. Er is geen manier om dit bijvoorbeeld te gebruiken met een ongelijkheidsoperator zoals $gt is het doel is om specifiek overeen te komen met de gevraagde "maat". Vandaar dit vraagformulier om alle mogelijke formaten op te geven die kleiner zijn dan.

$project :Er zijn een paar doelen in deze verklaring, waarvan sommige verschillen afhankelijk van de MongoDB-versie die je hebt. Ten eerste, en optioneel, wordt een documentkopie bewaard onder de _id waarde zodat deze velden niet worden gewijzigd door de rest van de stappen. Het andere deel hier is het bewaren van de "films"-array bovenaan het document als kopie voor de volgende fase.

Wat er ook gebeurt in de versie die wordt gepresenteerd voor versies van vóór 2.6, is dat er een extra array is die de _id vertegenwoordigt waarden voor de "films" die overeenkomen. Het gebruik van de $cond operator hier is slechts een manier om een ​​"letterlijke" weergave van de array te maken. Grappig genoeg introduceert MongoDB 2.6 een operator die bekend staat als $literal om precies dit te doen zonder de grappige manier waarop we $cond . gebruiken hier.

$unwind :Om iets verder te doen, moet de filmarray worden afgewikkeld, aangezien dit in beide gevallen de enige manier is om de bestaande _id te isoleren waarden voor de items die moeten worden vergeleken met de "set". Dus voor de pre 2.6-versie moet je beide aanwezige arrays "afwikkelen".

$groep :Voor MongoDB 2.6 en hoger groepeert u gewoon terug naar een array die alleen de _id bevat waarden van de films waarvan de "beoordelingen" zijn verwijderd.

Pre 2.6 omdat alle waarden "naast elkaar" worden gepresenteerd (en met veel duplicatie), doe je een vergelijking van de twee waarden om te zien of ze hetzelfde zijn. Waar is dat true , dit vertelt de $cond operator-instructie om een ​​waarde van 1 te retourneren of 0 waarbij de voorwaarde false is . Dit wordt direct teruggestuurd via $sum om het aantal overeenkomende elementen in de array op te tellen tot de vereiste "set".

$project :Waar dit het andere deel is voor MongoDB 2.6 en hoger, is dat aangezien je een array van de "movies" _id hebt teruggeduwd waarden die u dan gebruikt $setIntersection om die arrays direct te vergelijken. Omdat het resultaat hiervan een array is met de elementen die hetzelfde zijn, wordt dit vervolgens verpakt in een $size operator om te bepalen hoeveel elementen zijn geretourneerd in die overeenkomende set.

$match :is de laatste fase die hier is geïmplementeerd en die de duidelijke stap doet om alleen die documenten te matchen waarvan het aantal kruisende elementen groter was dan of gelijk was aan het vereiste aantal.

Finale

Dat is in principe hoe je het doet. Voorafgaand aan 2.6 is het wat onhandiger en vereist het wat meer geheugen vanwege de uitbreiding die wordt gedaan door elk arraylid dat wordt gevonden door alle mogelijke waarden van de set te dupliceren, maar het is nog steeds een geldige manier om dit te doen.

Het enige wat u hoeft te doen is dit toe te passen met de grotere n overeenkomende waarden om aan uw voorwaarden te voldoen, en zorg er natuurlijk voor dat uw originele gebruikersovereenkomst de vereiste n . heeft mogelijkheden. Genereer dit anders gewoon op n-1 van de lengte van de "user's" array van "movies".




  1. Lui laden in MongoDB met NoRM

  2. Hoe de verouderde ScriptDb vervangen door Mongodb met behulp van de URL Fetch-service?

  3. Belangrijkste verschillen/kenmerken van de meest bekende NoSQL-systemen

  4. beginnen adhoc-query's/updates uw productiviteit met MongoDB te vernietigen?