sql >> Databasteknik >  >> NoSQL >> MongoDB

Få filtrerat antal element i array från $lookup tillsammans med hela dokumentet

Anteckning för de som letar efter - Utländsk räkning

Lite bättre än vad som ursprungligen besvarades är att faktiskt använda den nyare formen av $lookup från MongoDB 3.6. Detta kan faktiskt göra "räkningen" inom "sub-pipeline"-uttrycket i motsats till att returnera en "array" för efterföljande filtrering och räkning eller till och med använda $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" }
  }}
])

Inte vad den ursprungliga frågan efterfrågade utan en del av svaret nedan i den nu mest optimala formen, som naturligtvis resultatet av $lookup reduceras till endast "matchat antal". istället för "alla matchade dokument".

Original

Det korrekta sättet att göra detta är att lägga till "linkCount" till $group skede samt en $first på eventuella ytterligare fält i det överordnade dokumentet för att få den "singular" formen som var tillståndet "före" $unwind bearbetades på arrayen som var resultatet av $lookup :

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

Producerar:

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

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

Producerar

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

Anledningen till att du gör det på det här sättet med en $unwind och sedan en $match beror på hur MongoDB faktiskt hanterar pipelinen när den utfärdas i den ordningen. Detta är vad som händer med $lookup som visas är "explain" utdata från operationen:

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

Jag lämnar delen med $group i den utgången för att visa att de andra två pipeline-stegen "försvinner". Detta beror på att de har "rullats upp" till $lookup rörledningssteg som visas. Det är faktiskt så MongoDB hanterar möjligheten att BSON-gränsen kan överskridas av resultatet av "joining"-resultat av $lookup i en array av det överordnade dokumentet.

Du kan alternativt skriva operationen så här:

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

Gruppera efter partiId

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 samma utdata men "skiljer sig" från den första frågan genom att $filter här tillämpas "efter" ALLA resultat av $lookup returneras till den nya arrayen i det överordnade dokumentet.

Så i prestandatermer är det faktiskt mer effektivt att göra det på första sättet samt att vara portabel till möjliga stora resultatuppsättningar "före filtrering" som annars skulle bryta 16MB BSON-gränsen.

Som en sidoanteckning för de som är intresserade, i framtida versioner av MongoDB (förmodligen 3.6 och uppåt) kan du använda $replaceRoot istället för en $addFields med användning av nya $mergeObjects rörledningsoperatör. Fördelen med detta är som ett "block", vi kan deklarera "filtrerad" innehåll som en variabel via $let , vilket innebär att du inte behöver skriva samma $filter "två gånger":

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

Icke desto mindre är det bästa sättet att göra sådana "filtrerade" $lookup driften är "fortfarande" för närvarande med $unwind sedan $match mönster, tills du kan tillhandahålla frågeargument till $ uppslag direkt.




  1. Jest och Redis (enhet testar problem med databascache)

  2. Alternativ för Mongoose autoReconnect

  3. Komplexa datastrukturer Redis

  4. Är det möjligt att byta namn på fält i utdata från en Mongo-fråga i PyMongo?