MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

MongoDB Nested Array Intersection Query

Es gibt mehrere Möglichkeiten, dies mit dem Aggregation Framework

zu tun

Nur ein einfacher Datensatz zum Beispiel:

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

Am Beispiel des ersten "Benutzers" möchten Sie nun herausfinden, ob einer der anderen beiden Benutzer mindestens zwei gleiche Filme hat.

Für MongoDB 2.6 und höher können Sie einfach den $setIntersection Operator zusammen mit dem $size Betreiber:

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

])

Dies ist in früheren Versionen von MongoDB, die diese Operatoren nicht haben, immer noch möglich, indem Sie nur ein paar weitere Schritte ausführen:

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

Im Detail

Das ist vielleicht ein bisschen zu verdauen, also können wir uns jede Phase ansehen und sie aufschlüsseln, um zu sehen, was sie tun.

$match :Sie möchten nicht jedes Dokument in der Sammlung bearbeiten, daher ist dies eine Gelegenheit, die Elemente zu entfernen, die möglicherweise nicht übereinstimmen, auch wenn noch mehr Arbeit zu tun ist, um das genaue zu finden Einsen. Das Offensichtliche ist also, denselben "Benutzer" auszuschließen und dann nur die Dokumente abzugleichen, die mindestens einen der gleichen Filme enthalten, die für diesen "Benutzer" gefunden wurden.

Das nächste, was Sinn macht, ist, dies zu berücksichtigen, wenn Sie n abgleichen möchten Einträge dann nur Dokumente, die ein "movies"-Array haben, das größer als n-1 ist kann möglicherweise tatsächlich Übereinstimmungen enthalten. Die Verwendung von $and sieht hier komisch aus und ist nicht ausdrücklich erforderlich, aber wenn die erforderlichen Übereinstimmungen 4 waren dann würde der eigentliche Teil der Anweisung so aussehen:

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

Sie "schließen" also im Grunde Arrays aus, die möglicherweise nicht lang genug sind, um n zu haben Streichhölzer. Beachten Sie hier, dass dieser $size Operator im Abfrageformular unterscheidet sich von $size für das Aggregationsframework. Es gibt beispielsweise keine Möglichkeit, dies mit einem Ungleichheitsoperator wie $gt zu verwenden Sein Zweck ist es, die angeforderte "Größe" genau zu treffen. Daher dieses Abfrageformular, um alle möglichen Größen anzugeben, die kleiner als sind.

$Projekt :Diese Anweisung enthält einige Zwecke, von denen sich einige je nach MongoDB-Version unterscheiden. Erstens und optional wird eine Dokumentenkopie unter der _id aufbewahrt Wert, damit diese Felder von den restlichen Schritten nicht geändert werden. Der andere Teil besteht darin, das "movies"-Array oben im Dokument als Kopie für die nächste Stufe aufzubewahren.

Was auch in der für Versionen vor 2.6 vorgestellten Version passiert, ist, dass es ein zusätzliches Array gibt, das die _id darstellt Werte für die "Filme" übereinstimmen. Die Verwendung von $cond Operator ist hier nur eine Möglichkeit, eine "wörtliche" Darstellung des Arrays zu erstellen. Witzigerweise führt MongoDB 2.6 einen Operator namens $literal ein um genau dies ohne die komische Art zu tun, verwenden wir $cond hier.

$Entspannen Hinweis:Um weitere Aktionen auszuführen, muss das Array films entpackt werden, da dies in jedem Fall die einzige Möglichkeit ist, die vorhandene _id zu isolieren Werte für die Einträge, die mit dem "Set" abgeglichen werden müssen. Für die Version vor 2.6 müssen Sie also beide vorhandenen Arrays "entladen".

$gruppe :Für MongoDB 2.6 und höher gruppieren Sie einfach zurück zu einem Array, das nur die _id enthält Werte der Filme, bei denen die "Ratings" entfernt wurden.

Vor 2.6, da alle Werte "nebeneinander" (und mit vielen Duplizierungen) dargestellt werden, führen Sie einen Vergleich der beiden Werte durch, um zu sehen, ob sie gleich sind. Wo das true ist , teilt dies $cond mit Operator-Anweisung, um einen Wert von 1 zurückzugeben oder 0 wobei die Bedingung false ist . Dies wird direkt über $sum zurückgereicht um die Anzahl der übereinstimmenden Elemente im Array auf die erforderliche "Menge" aufzusummieren.

$Projekt :Wo dies der unterschiedliche Teil für MongoDB 2.6 und höher ist, ist, dass Sie ein Array der "Filme" _id zurückgeschoben haben Werte, die Sie dann verwenden $setIntersection um diese Arrays direkt zu vergleichen. Als Ergebnis entsteht ein Array mit den gleichen Elementen, das dann in $size verpackt wird -Operator, um festzustellen, wie viele Elemente in diesem übereinstimmenden Satz zurückgegeben wurden.

$match :Ist die letzte Stufe, die hier implementiert wurde, die den klaren Schritt macht, nur die Dokumente abzugleichen, deren Anzahl sich überschneidender Elemente größer oder gleich der erforderlichen Anzahl war.

Ende

So macht man es im Grunde. Vor 2.6 ist es etwas umständlicher und benötigt etwas mehr Speicher aufgrund der Erweiterung, die durch Duplizieren jedes Array-Mitglieds erfolgt, das von allen möglichen Werten des Satzes gefunden wird, aber es ist immer noch ein gültiger Weg, dies zu tun.

Alles, was Sie tun müssen, ist dies mit dem größeren n anzuwenden Übereinstimmungswerte, um Ihre Bedingungen zu erfüllen, und stellen Sie natürlich sicher, dass Ihre ursprüngliche Benutzerübereinstimmung den erforderlichen n hat Möglichkeiten. Andernfalls generieren Sie dies einfach auf n-1 von der Länge des "user's"-Arrays von "movies".