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

So treten Sie zwei zusätzlichen Sammlungen mit Bedingungen bei

Was Sie hier vermissen, ist dieses $lookup erzeugt ein "Array" in dem durch as spezifizierten Ausgabefeld in seinen Argumenten. Dies ist das allgemeine Konzept der „Beziehungen“ von MongoDB, da eine „Beziehung“ zwischen Dokumenten als „Untereigenschaft“ dargestellt wird, die sich „innerhalb“ des Dokuments selbst befindet und für viele entweder singulär oder ein „Array“ ist.

Da MongoDB „schemalos“ ist, ist die allgemeine Annahme von $lookup ist, dass Sie "viele" meinen und das Ergebnis daher "immer" ein Array ist. Wenn Sie also nach dem "gleichen Ergebnis wie in SQL" suchen, müssen Sie $unwind ausführen dieses Array nach $lookup . Ob es "eins" oder "viele" ist, spielt keine Rolle, da es immer noch "immer" ein Array ist:

db.getCollection.('tb1').aggregate([
  // Filter conditions from the source collection
  { "$match": { "status": { "$ne": "closed" } }},

  // Do the first join
  { "$lookup": {
    "from": "tb2",
    "localField": "id",
    "foreignField": "profileId",
    "as": "tb2"
  }},

  // $unwind the array to denormalize
  { "$unwind": "$tb2" },

  // Then match on the condtion for tb2
  { "$match": { "tb2.profile_type": "agent" } },

  // join the second additional collection
  { "$lookup": {
    "from": "tb3",
    "localField": "tb2.id",
    "foreignField": "id",
    "as": "tb3"
  }},

  // $unwind again to de-normalize
  { "$unwind": "$tb3" },

  // Now filter the condition on tb3
  { "$match": { "tb3.status": 0 } },

  // Project only wanted fields. In this case, exclude "tb2"
  { "$project": { "tb2": 0 } }
])

Hier müssen Sie die anderen Dinge beachten, die Ihnen in der Übersetzung fehlen:

Reihenfolge ist "wichtig"

Aggregationspipelines sind "knapper aussagekräftiger" als SQL. Sie werden am besten als "eine Abfolge von Schritten" betrachtet auf die Datenquelle angewendet, um die Daten zu sammeln und zu transformieren. Das beste Analogon dazu sind "piped" Befehlszeilenanweisungen, wie zum Beispiel:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

Wo die "Pipe" | kann als „Pipeline-Stufe“ in einer MongoDB-Aggregations-„Pipeline“ betrachtet werden.

Als solches wollen wir $match um als erste Operation Dinge aus der Sammlung "Quelle" zu filtern. Dies ist im Allgemeinen eine bewährte Vorgehensweise, da alle Dokumente, die die erforderlichen Bedingungen nicht erfüllen, aus weiteren Bedingungen entfernt werden. Genauso wie in unserem „Befehlszeilen-Pipe“-Beispiel, wo wir „input“ und dann „pipe“ zu einem grep nehmen zum "Entfernen" oder "Filtern".

Pfade sind wichtig

Das nächste, was Sie hier tun, ist "beitreten" über $lookup . Das Ergebnis ist ein "Array" der Elemente aus "from" Sammlungsargument, das mit den bereitgestellten Feldern übereinstimmt, um in "as" ausgegeben zu werden "Feldpfad" als "Array".

Die hier gewählte Benennung ist wichtig, da das "Dokument" aus der Quellsammlung nun davon ausgeht, dass alle Elemente aus dem "Join" nun unter diesem angegebenen Pfad existieren. Um dies zu vereinfachen, verwende ich den gleichen "Sammlungs"-Namen wie der "Join" für den neuen "Pfad".

Beginnend mit dem ersten "Join" ist die Ausgabe also "tb2" und das wird alle Ergebnisse aus dieser Sammlung enthalten. Auch bei der folgenden Sequenz von $unwind ist etwas Wichtiges zu beachten und dann $match , wie MongoDB die Abfrage tatsächlich verarbeitet.

Bestimmte Sequenzen sind "wirklich" wichtig

Da es „aussieht“ gibt es „drei“ Pipeline-Stufen, nämlich $lookup dann $unwind und dann $match . Aber in Wirklichkeit macht MongoDB wirklich etwas anderes, was in der Ausgabe von { "explain": true } demonstriert wird zu .aggregate() hinzugefügt Befehl:

    {
        "$lookup" : {
            "from" : "tb2",
            "as" : "tb2",
            "localField" : "id",
            "foreignField" : "profileId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "profile_type" : {
                    "$eq" : "agent"
                }
            }
        }
    }, 
    {
        "$lookup" : {
            "from" : "tb3",
            "as" : "tb3",
            "localField" : "tb2.id",
            "foreignField" : "id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "status" : {
                    "$eq" : 0.0
                }
            }
        }
    }, 

Abgesehen vom ersten Punkt der Anwendung "sequence", wo Sie den $match einfügen müssen Aussagen dort treffen, wo sie gebraucht werden und das „Am meisten Gute“ bewirken, wird dies mit dem Begriff „Joins“ tatsächlich „wirklich wichtig“. Zu beachten ist hier, dass unsere Sequenzen von $lookup dann $unwind und dann $match , werden von MongoDB tatsächlich nur als $lookup verarbeitet Phasen, wobei die anderen Operationen jeweils in die eine Pipeline-Phase "aufgerollt" werden.

Dies ist ein wichtiger Unterschied zu anderen Möglichkeiten, die von $lookup erhaltenen Ergebnisse zu "filtern". . Denn in diesem Fall sind die eigentlichen "query"-Bedingungen auf den "join" von $match werden für die beizutretende Sammlung ausgeführt, "bevor" die Ergebnisse an das übergeordnete Element zurückgegeben werden.

Dies in Kombination mit $unwind ( was übersetzt wird in unwinding ), wie oben gezeigt, geht MongoDB tatsächlich mit der Möglichkeit um, dass die „Verknüpfung“ dazu führen könnte, dass ein Array von Inhalten im Quelldokument erstellt wird, wodurch die 16-MB-BSON-Grenze überschritten wird. Dies würde nur in Fällen passieren, in denen das zu verknüpfende Ergebnis sehr groß ist, aber der gleiche Vorteil besteht darin, dass der "Filter" tatsächlich angewendet wird, indem er auf der Zielsammlung "bevor" Ergebnisse zurückgegeben werden.

Diese Art der Behandlung "korreliert" am besten mit dem gleichen Verhalten wie ein SQL JOIN. Es ist daher auch der effektivste Weg, Ergebnisse aus einem $lookup zu erhalten wo neben den "lokalen" oder "fremden" Schlüsselwerten noch andere Bedingungen für den JOIN gelten.

Beachten Sie auch, dass die andere Verhaltensänderung von einem LEFT JOIN stammt, der von $lookup ausgeführt wird wobei das "Quell"-Dokument immer beibehalten würde, unabhängig davon, ob ein übereinstimmendes Dokument in der "Ziel"-Sammlung vorhanden ist. Stattdessen $unwind fügt dies hinzu, indem alle Ergebnisse aus der "Quelle" "verworfen" werden, die durch die zusätzlichen Bedingungen in $match keine Übereinstimmung mit dem "Ziel" hatten .

Tatsächlich werden sie aufgrund des impliziten preserveNullAndEmptyArrays: false sogar vorher verworfen die enthalten ist und alles verwerfen würde, wo die "lokalen" und "fremden" Schlüssel nicht einmal zwischen den beiden Sammlungen übereinstimmen. Dies ist eine gute Sache für diese spezielle Art von Abfrage, da das "Join" mit dem "Equal" bei diesen Werten beabsichtigt ist.

Schließen

Wie bereits erwähnt, behandelt MongoDB „Beziehungen“ im Allgemeinen ganz anders, als Sie eine „relationale Datenbank“ oder ein RDBMS verwenden würden. Das allgemeine Konzept von "Beziehungen" ist tatsächlich das "Einbetten" der Daten, entweder als einzelne Eigenschaft oder als Array.

Möglicherweise wünschen Sie sich tatsächlich eine solche Ausgabe, was auch ein Grund dafür ist, warum das ohne $unwind auskommt Sequenz hier die Ausgabe von $lookup ist eigentlich ein "Array". Allerdings mit $unwind in diesem Zusammenhang ist eigentlich das effektivste, was zu tun ist, und eine Garantie zu geben, dass die "verknüpften" Daten nicht tatsächlich dazu führen, dass die oben genannte BSON-Grenze als Ergebnis dieses "verknüpfens" überschritten wird.

Wenn Sie tatsächlich Arrays als Ausgabe haben möchten, verwenden Sie hier am besten die $group Pipeline-Phase und möglicherweise als mehrere Phasen, um die Ergebnisse von $unwind zu "normalisieren" und "rückgängig zu machen".

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

Wobei Sie in diesem Fall eigentlich alle benötigten Felder aus "tb1" auflisten würden durch ihre Eigenschaftsnamen mit $first nur das "erste" Vorkommen zu behalten (im Wesentlichen wiederholt durch Ergebnisse von "tb2" und "tb3" abgewickelt ) und dann $push das "Detail" aus "tb3" in ein "Array", um die Beziehung zu "tb1" darzustellen .

Die allgemeine Form der angegebenen Aggregationspipeline ist jedoch die genaue Darstellung, wie Ergebnisse aus dem ursprünglichen SQL erhalten würden, das als Ergebnis der "Verknüpfung" eine "denormalisierte" Ausgabe ist. Ob Sie die Ergebnisse danach wieder "normalisieren" möchten, bleibt Ihnen überlassen.