it-swarm.com.de

Mehrere Join-Bedingungen mit dem $ Lookup-Operator

Hier ist meine Sammlung:

collection1:

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

collection2:

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

Ich möchte die zwei Sammlungen von 'Benutzer1' und 'Benutzer2' verbinden.

Das Ergebnis wie folgt:

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

Wie schreibe ich die Pipeline?

12
user6148078

Mit dem Aggregationspipelineoperator $lookup in Version 3.6 und höher können wir mehrere Join-Bedingungen ausführen.

Wir müssen die Werte der Felder der Variablen mithilfe des optionalen Felds let zuweisen. Sie können dann auf diese Variablen in den Feldstufen pipeline zugreifen, in denen Sie die Pipeline angeben, die für die Sammlungen ausgeführt werden soll.

Beachten Sie, dass wir in der Phase $match den Auswertungsabfrageoperator $expr verwenden, um den Wert der Felder zu vergleichen.

Die letzte Phase in der Pipeline ist die Aggregationspipeline $replaceRoot , in der das Ergebnis $lookup einfach mit einem Teil des $$ROOT-Dokuments mit dem Operator $mergeObjects zusammengeführt wird.

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

Diese Pipeline führt zu etwas, das so aussieht:

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

Wenn Sie nicht über Version 3.6 oder höher verfügen, können Sie zuerst mit einem Ihrer Felder beitreten, sagen Sie "user1". Dann lösen Sie das Array des übereinstimmenden Dokuments mit dem Aggregationspipelineoperator $unwind auf. Die nächste Stufe in der Pipeline ist die Phase $redact , in der Sie die Dokumente herausfiltern, bei denen der Wert "user2" aus der Sammlung "join" und das Eingabedokument nicht gleich sind. Verwenden Sie dazu die Befehle $$KEEP und . $$Prune Systemvariablen. Sie können Ihr Dokument dann in der Phase $project umformen.

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

was produziert:

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

Wenn die Dokumente in Ihren Sammlungen dieselbe Struktur haben und Sie diesen Vorgang häufig durchführen, sollten Sie die beiden Sammlungen in einer zusammenführen oder die Dokumente in diesen Sammlungen in eine neue Sammlung einfügen.

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

Dann $group Ihre Dokumente nach "user1" und "user2"

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

was ergibt:

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

Wenn Sie versuchen, Ihre Daten zu modellieren, und hierher gekommen sind, um zu prüfen, ob mongodb Joins in mehreren Feldern ausführen kann, bevor Sie sich dazu entscheiden, lesen Sie bitte weiter.

Während MongoDB Joins ausführen kann, haben Sie auch die Möglichkeit, Daten gemäß Ihrem Anwendungszugriffsmuster zu modellieren. Wenn die Daten so einfach sind wie in der Frage dargestellt, können wir einfach eine einzelne Sammlung pflegen, die wie folgt aussieht:

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

Jetzt können Sie alle Vorgänge für diese Sammlung ausführen, die Sie beim Beitreten ausgeführt hätten. Warum versuchen wir, Joins zu vermeiden? Sie werden nicht von Sharded-Sammlungen ( docs ) unterstützt, sodass Sie bei Bedarf nicht skaliert werden können. Die Normalisierung von Daten (mit separaten Tabellen/Sammlungen) funktioniert in SQL sehr gut. Wenn Sie jedoch Mongo verwenden, kann das Vermeiden von Joins in den meisten Fällen Vorteile ohne Folgen haben. Verwenden Sie die Normalisierung in MongoDB nur, wenn Sie keine andere Wahl haben. Aus den docs :

Verwenden Sie im Allgemeinen normalisierte Datenmodelle:

  • ein Einbetten würde zu einer Duplizierung von Daten führen, würde jedoch keine ausreichenden Leseleistungsvorteile bieten, um die Auswirkungen der Duplizierung zu überwiegen.
  • komplexere Viele-zu-Viele-Beziehungen darstellen.
  • große hierarchische Datensätze zu modellieren.

Überprüfen Sie hier , um mehr über das Einbetten zu erfahren und warum Sie es über die Normalisierung entscheiden würden.

2
Andrew Nessin

Sie können mehrere Feldübereinstimmungen mit den Pipelines $ match und $ project durchführen. (Siehe ausführliche Antwort hier - mongoDB Join in mehreren Feldern )

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