sql >> Database >  >> NoSQL >> MongoDB

Krijg een gefilterd aantal elementen in de array van $lookup samen met het hele document

Annotatie voor wie op zoek is naar - Buitenlandse graaf

Een beetje beter dan oorspronkelijk werd beantwoord, is om de nieuwere vorm van $opzoeken van MongoDB 3.6. Dit kan het "tellen" doen binnen de "sub-pipeline"-expressie in tegenstelling tot het retourneren van een "array" voor daaropvolgende filtering en telling of zelfs het gebruik van $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" }
  }}
])

Niet waar de oorspronkelijke vraag om vroeg, maar een deel van het onderstaande antwoord in de nu meest optimale vorm, zoals natuurlijk het resultaat van $lookup wordt teruggebracht tot het "overeenkomende aantal" alleen in plaats van "alle overeenkomende documenten".

Origineel

De juiste manier om dit te doen is door de "linkCount" . toe te voegen naar de $group stage en een $first op eventuele aanvullende velden van het bovenliggende document om de "enkelvoud" vorm te krijgen, net als de staat "voor" de $unwind is verwerkt op de array die het resultaat was van $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", [] ]
        } 
      }   
    }
  }}
])

Produceert:

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

Groep op 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", [] ]
        } 
      }   
    }
  }}
])

Produceert

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

De reden waarom je het op deze manier doet met een $unwind en dan een $match is vanwege de manier waarop MongoDB de pijplijn daadwerkelijk afhandelt wanneer deze in die volgorde wordt uitgegeven. Dit is wat er gebeurt met de $lookup zoals wordt aangetoond de "explain" output van de operatie:

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

Ik verlaat het deel met $group in die uitvoer om aan te tonen dat de andere twee pijplijntrappen "verdwijnen". Dit komt omdat ze zijn "opgerold" in de $opzoeken pijplijnfase zoals afgebeeld. Dit is in feite hoe MongoDB omgaat met de mogelijkheid dat de BSON-limiet kan worden overschreden door het resultaat van "joining" resultaten van $lookup in een array van het bovenliggende document.

U kunt de bewerking ook als volgt schrijven:

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

Groeperen op partId

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

Die dezelfde uitvoer heeft, maar "afwijkt" van de eerste query doordat de $filter hier wordt toegepast "na" ALLE resultaten van de $lookup worden geretourneerd in de nieuwe array van het bovenliggende document.

Dus in prestatietermen is het eigenlijk effectiever om het op de eerste manier te doen, maar het is ook overdraagbaar naar mogelijke grote resultatensets "vóór het filteren", die anders de 16 MB BSON-limiet zouden breken.

Als een kanttekening voor degenen die geïnteresseerd zijn, kun je in toekomstige releases van MongoDB (vermoedelijk 3.6 en hoger) $replaceRoot in plaats van een $addFields met gebruik van de nieuwe $mergeObjects pijpleiding exploitant. Het voordeel hiervan is dat we als "blok" de "gefilterd" kunnen declareren inhoud als een variabele via $let , wat betekent dat u niet dezelfde $filter hoeft te schrijven "tweemaal":

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

Niettemin is de beste manier om dergelijke "gefilterde" $lookup operations is op dit moment "still" met behulp van de $unwind dan $match patroon, totdat u queryargumenten kunt verstrekken aan $ opzoeken rechtstreeks.




  1. MongoDB-aggregatie:veld toevoegen uit een ingesloten document via een dynamisch veldpad

  2. Hoe $cond-bewerking te gebruiken in het aggregatieraamwerk van Spring-MongoDb

  3. Lente Mongodb Tijdstempel Tijdzone Misleidend

  4. MongoDB $dateToString Formaatspecificaties