web-dev-qa-db-fra.com

Conditions de jointure multiples à l'aide de l'opérateur $ lookup

Voici ma collection:

collection1:

{
    user1: 1,
    user2: 2,
    percent: 0.56
}

collection2:

{
    user1: 1,
    user2: 2,
    percent: 0.3
}

Je veux rejoindre les deux collections par 'user1' et 'user2'.

Le résultat ressemble à ceci:

{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}

Comment puis-je écrire le pipeline?

12
user6148078

Nous pouvons créer plusieurs conditions de jointure avec l'opérateur de pipeline d'agrégation $lookup dans la version 3.6 et plus récente.

Nous devons affecter les valeurs des champs à la variable à l'aide du champ optionnel let; vous accédez ensuite à ces variables dans les étapes du champ pipeline où vous spécifiez le pipeline à exécuter sur les collections.

Notez qu'à l'étape $match, nous utilisons l'opérateur de requête $expr evaluation pour comparer la valeur des champs.

La dernière étape du pipeline est l'étape $replaceRoot agrégation où nous fusionnons simplement le résultat $lookup avec une partie du document $$ROOT à l'aide de l'opérateur $mergeObjects .

db.collection2.aggregate([
       {
          $lookup: {
             from: "collection1",
             let: {
                firstUser: "$user1",
                secondUser: "$user2"
             },
             pipeline: [
                {
                   $match: {
                      $expr: {
                         $and: [
                            {
                               $eq: [
                                  "$user1",
                                  "$$firstUser"
                               ]
                            },
                            {
                               $eq: [
                                  "$user2",
                                  "$$secondUser"
                               ]
                            }
                         ]
                      }
                   }
                }
             ],
             as: "result"
          }
       },
       {
          $replaceRoot: {
             newRoot: {
                $mergeObjects:[
                   {
                      $arrayElemAt: [
                         "$result",
                         0
                      ]
                   },
                   {
                      percent1: "$$ROOT.percent1"
                   }
                ]
             }
          }
       }
    ]
)

Ce pipeline produit quelque chose qui ressemble à ceci:

{
    "_id" : ObjectId("59e1ad7d36f42d8960c06022"),
    "user1" : 1,
    "user2" : 2,
    "percent" : 0.3,
    "percent1" : 0.56
}

Si vous n'êtes pas sous la version 3.6+, vous pouvez d'abord rejoindre en utilisant l'un de vos champs en indiquant "utilisateur1", puis en déroulant le tableau du document correspondant à l'aide de l'opérateur de pipeline $unwind agrégation. L'étape suivante du pipeline est l'étape $redact , dans laquelle vous filtrez les documents pour lesquels la valeur "utilisateur2" de la collection "jointe" et le document d'entrée ne sont pas égaux à l'aide de $$KEEP et $$Prune variables système. Vous pouvez ensuite remodeler votre document dans $project stage.

db.collection1.aggregate([
    { "$lookup": { 
        "from": "collection2", 
        "localField": "user1", 
        "foreignField": "user1", 
        "as": "collection2_doc"
    }}, 
    { "$unwind": "$collection2_doc" },
    { "$redact": { 
        "$cond": [
            { "$eq": [ "$user2", "$collection2_doc.user2" ] }, 
            "$$KEEP", 
            "$$Prune"
        ]
    }}, 
    { "$project": { 
        "user1": 1, 
        "user2": 1, 
        "percent1": "$percent", 
        "percent2": "$collection2_doc.percent"
    }}
])

qui produit:

{
    "_id" : ObjectId("572daa87cc52a841bb292beb"),
    "user1" : 1,
    "user2" : 2,
    "percent1" : 0.56,
    "percent2" : 0.3
}

Si les documents de vos collections ont la même structure et que vous effectuez souvent cette opération, envisagez de fusionner les deux collections en une seule ou d'insérer les documents de ces collections dans une nouvelle collection.

db.collection3.insertMany(
    db.collection1.find({}, {"_id": 0})
    .toArray()
    .concat(db.collection2.find({}, {"_id": 0}).toArray())
)

Puis $group vos documents par "utilisateur1" et "utilisateur2"

db.collection3.aggregate([
    { "$group": {
        "_id": { "user1": "$user1", "user2": "$user2" }, 
        "percent": { "$Push": "$percent" }
    }}
])

qui donne:

{ "_id" : { "user1" : 1, "user2" : 2 }, "percent" : [ 0.56, 0.3 ] }
34
styvane

Si vous essayez de modéliser vos données et que vous êtes venu ici pour vérifier si mongodb peut effectuer des jointures sur plusieurs champs avant de décider de le faire, lisez la suite.

Bien que MongoDB puisse effectuer des jointures, vous avez également la liberté de modéliser des données en fonction de votre modèle d'accès à l'application. Si les données sont aussi simples que présentées dans la question, nous pouvons simplement gérer une collection unique qui ressemble à ceci:

{
    user1: 1,
    user2: 2,
    percent1: 0.56,
    percent2: 0.3
}

Vous pouvez maintenant effectuer toutes les opérations sur cette collection que vous auriez effectuées en vous joignant. Pourquoi essayons-nous d'éviter les jointures? Parce qu'ils ne sont pas pris en charge par les collections fragmentées ( docs ), ce qui vous empêche de procéder à une mise à l'échelle en cas de besoin. La normalisation des données (avec des tables/collections séparées) fonctionne très bien en SQL, mais s’agissant de Mongo, éviter les jointures peut offrir des avantages sans conséquences dans la plupart des cas. Utilisez la normalisation dans MongoDB uniquement lorsque vous n'avez pas d'autre choix. De la docs :

En général, utilisez des modèles de données normalisés:

  • lors de l’incorporation, il en résulterait une duplication des données mais ne procurerait pas suffisamment d’avantages en termes de performances de lecture pour compenser les conséquences de la duplication.
  • pour représenter des relations plusieurs à plusieurs plus complexes.
  • pour modéliser de grands ensembles de données hiérarchiques.

Consultez ici pour en savoir plus sur l’incorporation et sur la raison pour laquelle vous choisiriez cette option plutôt que sur la normalisation.

2
Andrew Nessin

Vous pouvez effectuer plusieurs correspondances de champ à l'aide des pipelines $ match et $ project. (voir réponse détaillée ici - mongoDB Join sur plusieurs champs )

db.collection1.aggregate([
                    {"$lookup": {
                    "from": "collection2",
                    "localField": "user1",
                    "foreignField": "user1",
                    "as": "c2"
                    }},
                    {"$unwind": "$c2"},

                    {"$project": {
                    "user2Eq": {"$eq": ["$user2", "$c2.user2"]},
                    "user1": 1, "user2": 1, 
                    "percent1": "$percent", "percent2": "$c2.percent"
                    }},

                    {"$match": {
                    {"user2Eq": {"$eq": True}}
                    }},

                    {"$project": {
                    "user2Eq": 0
                    }}

                    ])
1
Shaurabh Bharti