sql >> Database teknologi >  >> NoSQL >> MongoDB

Få filtreret antal elementer i array fra $lookup sammen med hele dokumentet

Annotation til dem, der leder efter - Udenlandsk Count

En smule bedre end det oprindeligt blev besvaret er faktisk at bruge den nyere form for $lookup fra MongoDB 3.6. Dette kan faktisk "tælle" i "sub-pipeline" udtrykket i modsætning til at returnere en "array" til efterfølgende filtrering og optælling eller endda bruge $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" }
  }}
])

Ikke hvad det oprindelige spørgsmål bad om, men en del af nedenstående svar i den nu mest optimale form, som selvfølgelig resultatet af $lookup reduceres kun til "matchede antal". i stedet for "alle matchede dokumenter".

Original

Den korrekte måde at gøre dette på ville være at tilføje "linkCount" til $group fase samt en $first på eventuelle yderligere felter i det overordnede dokument for at få "ental"-formen, som var tilstanden "før" $unwind blev behandlet på det array, der var resultatet af $lookup :

Alle detaljer

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

Producerer:

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

Gruppér efter 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", [] ]
        } 
      }   
    }
  }}
])

Producerer

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

Grunden til at du gør det på denne måde med en $unwind og derefter en $match er på grund af, hvordan MongoDB faktisk håndterer pipelinen, når den udstedes i den rækkefølge. Dette er, hvad der sker med $lookup som det er demonstreret, er "explain" output fra operationen:

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

Jeg forlader delen med $group i det output for at demonstrere, at de to andre pipeline-faser "forsvinder". Dette skyldes, at de er blevet "rullet op" i $lookup rørledningstrin som vist. Det er faktisk sådan, MongoDB håndterer muligheden for, at BSON-grænsen kan overskrides af resultatet af "joining"-resultater af $lookup ind i en matrix af det overordnede dokument.

Du kan skiftevis skrive handlingen sådan her:

Alle detaljer

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

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

Som har samme output, men "adskiller sig" fra den første forespørgsel ved, at $filter her anvendes "efter" ALLE resultater af $lookup returneres til det nye array i det overordnede dokument.

Så med hensyn til ydeevne er det faktisk mere effektivt at gøre det på den første måde samt at være bærbart til mulige store resultatsæt "før filtrering", som ellers ville bryde 16 MB BSON-grænsen.

Som en sidebemærkning til dem, der er interesserede, kan du i fremtidige udgivelser af MongoDB (formentlig 3.6 og nyere) bruge $replaceRoot i stedet for en $addFields med brug af de nye $mergeObjects rørledningsoperatør. Fordelen ved dette er som en "blok", vi kan erklære "filtreret" indhold som en variabel via $let , hvilket betyder, at du ikke behøver at skrive det samme $filter "to gange":

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

Ikke desto mindre er den bedste måde at gøre sådan en "filtreret" $lookup driften er "stadig" på nuværende tidspunkt ved hjælp af $unwind derefter $match mønster, indtil du kan give forespørgselsargumenter til $ opslag direkte.




  1. GeoLocation API Kalder mod en EVE RESTful API

  2. Sådan fanges undtagelse, når du opretter MongoClient-instans

  3. MongoDB-sammenlægning på Loopback

  4. Hvordan fungerer Redis PubSub-abonnementsmekanismen?