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

Holen Sie sich die gefilterte Anzahl der Elemente im Array von $lookup zusammen mit dem gesamten Dokument

Anmerkung für Suchende - Ausländische Zählung

Ein bisschen besser als ursprünglich beantwortet, ist es, tatsächlich die neuere Form von $nachschlagen von MongoDB 3.6. Dies kann tatsächlich das „Zählen“ innerhalb des „Sub-Pipeline“-Ausdrucks übernehmen, anstatt ein „Array“ zum anschließenden Filtern und Zählen zurückzugeben oder sogar $unwind

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "let": { "id": "$_id" },
    "pipeline": [
      { "$match": {
        "originalLink": "",
        "$expr": { "$eq": [ "$$id", "$_id" ] }
      }},
      { "$count": "count" }
    ],
    "as": "linkCount"    
  }},
  { "$addFields": {
    "linkCount": { "$sum": "$linkCount.count" }
  }}
])

Nicht das, wonach die ursprüngliche Frage gefragt wurde, aber ein Teil der folgenden Antwort in der jetzt optimalsten Form, wie natürlich das Ergebnis von $lookup wird nur auf die "übereinstimmende Anzahl" reduziert statt "alle übereinstimmenden Dokumente".

Original

Der richtige Weg, dies zu tun, wäre, den "linkCount" hinzuzufügen zu $group Stufe sowie ein $first auf allen zusätzlichen Feldern des übergeordneten Dokuments, um die "Singular"-Form zu erhalten, wie es der Zustand "vor" dem $unwind wurde auf dem Array verarbeitet, das das Ergebnis von $lookup :

Alle Details

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$unwind": "$link" },
  { "$match": { "link.originalLink": "" } },
  { "$group": {
    "_id": "$_id",
    "partId": { "$first": "$partId" },
    "link": { "$push": "$link" },
    "linkCount": {
      "$sum": {
        "$size": {
          "$ifNull": [ "$link.linkHistory", [] ]
        } 
      }   
    }
  }}
])

Erzeugt:

{
    "_id" : ObjectId("594a6c47f51e075db713ccb6"),
    "partId" : "f56c7c71eb14a20e6129a667872f9c4f",
    "link" : [ 
        {
            "_id" : ObjectId("594b96d6f51e075db67c44c9"),
            "originalLink" : "",
            "emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
            "linkHistory" : [ 
                {
                    "_id" : ObjectId("594b96f5f51e075db713ccdf")
                }, 
                {
                    "_id" : ObjectId("594b971bf51e075db67c44ca")
                }
            ]
        }
    ],
    "linkCount" : 2
}

Gruppieren nach partId

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$unwind": "$link" },
  { "$match": { "link.originalLink": "" } },
  { "$group": {
    "_id": "$partId",
    "linkCount": {
      "$sum": {
        "$size": {
          "$ifNull": [ "$link.linkHistory", [] ]
        } 
      }   
    }
  }}
])

Produziert

{
    "_id" : "f56c7c71eb14a20e6129a667872f9c4f",
    "linkCount" : 2
}

Der Grund, warum Sie es auf diese Weise mit einem $unwind und dann ein $match liegt daran, wie MongoDB die Pipeline tatsächlich handhabt, wenn sie in dieser Reihenfolge ausgegeben wird. Das passiert mit $lookup wie der "explain" demonstriert Ausgabe der Operation:

    {
        "$lookup" : {
            "from" : "link",
            "as" : "link",
            "localField" : "_id",
            "foreignField" : "emailGroupId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "originalLink" : {
                    "$eq" : ""
                }
            }
        }
    }, 
    {
        "$group" : {

Ich verlasse den Teil mit $group in dieser Ausgabe, um zu demonstrieren, dass die anderen beiden Pipeline-Stufen "verschwinden". Dies liegt daran, dass sie in den "aufgerollt" wurden $nachschlagen Pipeline-Stufe wie abgebildet. Auf diese Weise geht MongoDB tatsächlich mit der Möglichkeit um, dass das BSON-Limit durch das Ergebnis der "Verknüpfung" von Ergebnissen von $lookup in ein Array des übergeordneten Dokuments.

Alternativ können Sie die Operation auch so schreiben:

Alle Details

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$addFields": {
    "link": {
      "$filter": {
        "input": "$link",
        "as": "l",
        "cond": { "$eq": [ "$$l.originalLink", "" ] }    
      }
    },
    "linkCount": {
      "$sum": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$link",
              "as": "l",
              "cond": { "$eq": [ "$$l.originalLink", "" ] }
            }
          },
          "as": "l",
          "in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
        }     
      }
    }    
  }}
])

Nach partId gruppieren

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$addFields": {
    "link": {
      "$filter": {
        "input": "$link",
        "as": "l",
        "cond": { "$eq": [ "$$l.originalLink", "" ] }    
      }
    },
    "linkCount": {
      "$sum": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$link",
              "as": "l",
              "cond": { "$eq": [ "$$l.originalLink", "" ] }
            }
          },
          "as": "l",
          "in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
        }     
      }
    }    
  }},
  { "$unwind": "$link" },
  { "$group": {
    "_id": "$partId",
    "linkCount": { "$sum": "$linkCount" } 
  }}
])

Welche die gleiche Ausgabe hat, sich aber von der ersten Abfrage dadurch "unterscheidet", dass $filter hier wird "after" ALL angewendet Ergebnisse von $lookup werden in das neue Array des übergeordneten Dokuments zurückgegeben.

In Bezug auf die Leistung ist es also tatsächlich effektiver, es auf die erste Art zu tun und auf mögliche große Ergebnismengen "vor dem Filtern" portierbar zu sein, die andernfalls die 16-MB-BSON-Grenze überschreiten würden.

Als Randnotiz für diejenigen, die daran interessiert sind, können Sie in zukünftigen Versionen von MongoDB (vermutlich 3.6 und höher) $replaceRoot anstelle eines $addFields mit Verwendung der neuen $mergeObjects Pipeline-Betreiber. Der Vorteil davon ist, dass wir als "Block" den "gefiltert" deklarieren können Inhalt als Variable über $let , was bedeutet, dass Sie nicht denselben $filter schreiben müssen "zweimal":

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$replaceRoot": {
    "newRoot": {
      "$mergeObjects": [
        "$$ROOT",
        { "$let": {
          "vars": {
            "filtered": {
              "$filter": {
                "input": "$link",
                "as": "l",
                "cond": { "$eq": [ "$$l.originalLink", "" ] }    
              }
            }
          },
          "in": {
            "link": "$$filtered",
            "linkCount": {
              "$sum": {
                "$map": {
                  "input": "$$filtered.linkHistory",
                  "as": "lh",
                  "in": { "$size": { "$ifNull": [ "$$lh", [] ] } } 
                }   
              } 
            }  
          }
        }}
      ]
    }
  }}
])

Nichtsdestotrotz ist der beste Weg, solche "gefilterten" $lookup Der Betrieb erfolgt derzeit „noch“ unter Verwendung von $unwind dann $match Muster, bis Sie $ Abfrageargumente bereitstellen können nachschlagen direkt.