MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Requête d'intersection de tableaux imbriqués MongoDB

Il existe plusieurs façons de procéder à l'aide du cadre d'agrégation

Juste un simple ensemble de données par exemple :

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

En utilisant le premier "utilisateur" comme exemple, vous voulez maintenant savoir si l'un des deux autres utilisateurs a au moins deux des mêmes films.

Pour MongoDB 2.6 et versions ultérieures, vous pouvez simplement utiliser le $setIntersection opérateur avec $size opérateur :

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

])

Cela est toujours possible dans les versions antérieures de MongoDB qui n'ont pas ces opérateurs, en utilisant simplement quelques étapes supplémentaires :

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

En détail

Cela peut être un peu à prendre en compte, nous pouvons donc jeter un œil à chaque étape et les décomposer pour voir ce qu'ils font.

$match  :Vous ne souhaitez pas opérer sur tous les documents de la collection, c'est donc l'occasion de supprimer les éléments qui ne correspondent peut-être pas même s'il reste encore du travail à faire pour trouver l'exact ceux. Donc, les choses évidentes sont d'exclure le même "utilisateur" et de ne faire correspondre que les documents qui ont au moins un des mêmes films que ceux trouvés pour cet "utilisateur".

La prochaine chose qui a du sens est de considérer que lorsque vous voulez faire correspondre n entrées alors seulement les documents qui ont un tableau "movies" qui est plus grand que n-1 peut éventuellement contenir des correspondances. L'utilisation de $and ici semble drôle et n'est pas spécifiquement requis, mais si les correspondances requises étaient 4 alors cette partie réelle de la déclaration ressemblerait à ceci :

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

Donc, vous "excluez" les tableaux qui ne sont peut-être pas assez longs pour avoir n allumettes. Notant ici que cela $size l'opérateur dans le formulaire de requête est différent de $size pour le cadre d'agrégation. Il n'y a aucun moyen, par exemple, d'utiliser ceci avec un opérateur d'inégalité tel que $gt est son but est de correspondre spécifiquement à la "taille" demandée. D'où ce formulaire de requête pour spécifier toutes les tailles possibles qui sont inférieures à.

$projet :Il y a quelques objectifs dans cette déclaration, dont certains diffèrent selon la version de MongoDB que vous avez. Tout d'abord, et facultativement, une copie du document est conservée sous le _id valeur afin que ces champs ne soient pas modifiés par le reste des étapes. L'autre partie ici consiste à conserver le tableau "films" en haut du document en tant que copie pour l'étape suivante.

Ce qui se passe également dans la version présentée pour les versions antérieures à 2.6, c'est qu'il existe un tableau supplémentaire représentant le _id valeurs pour les "films" à faire correspondre. L'utilisation du $cond L'opérateur ici n'est qu'un moyen de créer une représentation "littérale" du tableau. Assez drôle, MongoDB 2.6 introduit un opérateur appelé $literal pour faire exactement cela sans la drôle de façon dont nous utilisons $cond juste ici.

$détendez-vous  :Pour faire quoi que ce soit d'autre, le tableau de films doit être déroulé car dans les deux cas, c'est le seul moyen d'isoler le _id existant valeurs pour les entrées qui doivent être mises en correspondance avec "l'ensemble". Donc, pour la version antérieure à 2.6, vous devez "dérouler" les deux tableaux présents.

$groupe :Pour MongoDB 2.6 et supérieur, vous ne faites que vous regrouper dans un tableau qui ne contient que le _id valeurs des films avec les "notes" supprimées.

Avant 2.6, puisque toutes les valeurs sont présentées "côte à côte" (et avec beaucoup de doublons), vous effectuez une comparaison des deux valeurs pour voir si elles sont identiques. Où c'est true , cela indique au $cond instruction de l'opérateur pour renvoyer une valeur de 1 ou 0 où la condition est false . Ceci est directement renvoyé via $sum pour totaliser le nombre d'éléments correspondants dans le tableau à l'"ensemble" requis.

$projet :Là où il s'agit de la partie différente pour MongoDB 2.6 et supérieur, c'est que depuis que vous avez repoussé un tableau des "films" _id valeurs que vous utilisez alors $setIntersection pour comparer directement ces tableaux. Comme le résultat est un tableau contenant les éléments identiques, il est ensuite enveloppé dans un $size afin de déterminer combien d'éléments ont été renvoyés dans cet ensemble correspondant.

$match :Est-ce la dernière étape qui a été implémentée ici qui fait l'étape claire de ne faire correspondre que les documents dont le nombre d'éléments qui se croisent était supérieur ou égal au nombre requis.

Finale

C'est en gros comme ça qu'on fait. Avant 2.6, c'est un peu plus maladroit et nécessitera un peu plus de mémoire en raison de l'expansion qui se fait en dupliquant chaque membre du tableau trouvé par toutes les valeurs possibles de l'ensemble, mais c'est toujours un moyen valable de le faire.

Tout ce que vous avez à faire est d'appliquer ceci avec le plus grand n les valeurs correspondantes pour répondre à vos conditions, et bien sûr assurez-vous que votre correspondance d'utilisateur d'origine a le n requis possibilités. Sinon, générez simplement ceci sur n-1 à partir de la longueur du tableau de "films" de "l'utilisateur".