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

Trouver deux documents dans MongoDB qui partagent une valeur clé

Bien que je maintienne les commentaires selon lesquels je ne pense pas que la façon dont vous formulez votre question soit réellement liée à un problème spécifique que vous rencontrez, je vais expliquer d'une manière ou d'une autre la manière idiomatique SQL dans un type de solution MongoDB. Je suis convaincu que votre solution réelle serait différente, mais vous ne nous avez pas présenté ce problème, mais uniquement SQL.

Considérez donc les documents suivants comme un ensemble d'échantillons, en supprimant les champs _id dans cette liste pour plus de clarté :

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }
{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }
{ "name" : "z", "type" : "z" }

Si nous exécutions le SQL présenté sur les mêmes données, nous obtiendrions ce résultat :

a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c

Nous pouvons voir que 2 documents ne correspondent pas, puis élaborer la logique de l'opération SQL. Donc, l'autre façon de le dire est "Quels documents donnés une clé de "nom" font avoir plus d'un valeur possible dans la clé "type".

Étant donné que, en adoptant une approche mongo, nous pouvons interroger les éléments qui ne le font pas correspondre à la condition donnée. Donc, effectivement, l'inverse du résultat :

db.sample.aggregate([

    // Store unique documents grouped by the "name"
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type" 
            }
        } 
    }},

    // Unwind the "set" results
    {$unwind: "$comp"},

    // Push the results back to get the unique count
    // *note* you could not have done this with alongside $addtoSet
    {$group: {
        _id: "$_id",
        comp: {
            $push: { 
                name: "$comp.name",
                type: "$comp.type" 
            }
        },
        count: {$sum: 1} 
    }},

    // Match only what was counted once
    {$match: {count: 1}},

    // Unwind the array
    {$unwind: "$comp"},

    // Clean up to "name" and "type" only
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}

])

Cette opération donnera les résultats :

{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }

Maintenant, pour obtenir le même résultat que la requête SQL, nous prendrions ces résultats et les canaliserions dans une autre requête :

db.sample.find({$nor: [{ name: "f", type: "e"},{ name: "z", type: "z"}] })

Qui arrive comme résultat de correspondance final :

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }

Cela fonctionnera donc, mais la seule chose qui peut rendre cela peu pratique est le nombre de documents à comparer est très grand, nous atteignons une limite de travail pour compacter ces résultats dans un tableau.

Il souffre aussi un peu de l'utilisation d'un négatif dans l'opération de recherche finale qui forcerait une analyse de la collection. Mais en toute honnêteté, on pourrait en dire autant de la requête SQL qui utilise le même négatif prémisse.

Modifier

Bien sûr, ce que je n'ai pas mentionné, c'est que si le jeu de résultats va dans l'autre sens et que vous correspondez plus résultats dans les éléments exclus de l'agrégat, puis inversez simplement la logique pour obtenir les clés souhaitées. Modifiez simplement $match comme suit :

{$match: {$gt: 1}}

Et ce sera le résultat, peut-être pas les documents réels, mais c'est un résultat. Vous n'avez donc pas besoin d'une autre requête pour faire correspondre les cas négatifs.

Et, finalement, c'était de ma faute parce que j'étais tellement concentré sur la traduction idiomatique que je n'ai pas lu la dernière ligne de votre question, où faire dites que vous en cherchiez un document.

Bien sûr, actuellement si la taille du résultat est supérieure à 16 Mo, vous êtes bloqué. Au moins jusqu'à la 2.6 version, où les résultats des opérations d'agrégation sont un curseur , vous pouvez donc itérer cela comme un .find() .

Également introduit dans 2.6 est le $size opérateur qui permet de trouver la taille d'un tableau dans le document. Cela aiderait donc à supprimer le deuxième $unwind et $group qui sont utilisés pour obtenir la longueur de l'ensemble. Cela modifie la requête en une forme plus rapide :

db.sample.aggregate([
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type"
            }
        } 
    }},
    {$project: { 
        comp: 1,
        count: {$size: "$comp"} 
    }},
    {$match: {count: {$gt: 1}}},
    {$unwind: "$comp"},
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}
])

Et MongoDB 2.6.0-rc0 est actuellement disponible si vous le faites uniquement pour un usage personnel ou pour le développement/test.

Morale de l'histoire. Oui, vous pouvez fais-le, Mais êtes-vous vraiment envie ou besoin faire comme ça ? Alors probablement pas, et si vous posez une question différente sur l'analyse de rentabilisation spécifique, vous obtiendrez peut-être une réponse différente. Mais encore une fois, cela peut être exactement ce que vous voulez.

Remarque

Il convient de mentionner que lorsque vous regardez les résultats du SQL, il sera dupliqué par erreur plusieurs éléments en raison des autres options de type disponibles si vous n'avez pas utilisé de DISTINCT pour ces valeurs ou essentiellement un autre groupement. Mais c'est le résultat qui a été produit par ce processus utilisant MongoDB.

Pour Alexandre

Voici la sortie de l'agrégat dans le shell des versions 2.4.x actuelles :

{
    "result" : [
            {
                    "name" : "f",
                    "type" : "e"
            },
            {
                    "name" : "z",
                    "type" : "z"
            }
    ],
    "ok" : 1
}

Faites ceci pour obtenir un var à passer comme argument à la condition $nor dans la deuxième recherche, comme ceci :

var cond = db.sample.aggregate([ .....

db.sample.find({$nor: cond.result })

Et vous devriez obtenir les mêmes résultats. Sinon consultez votre chauffeur.