sql >> Databáze >  >> NoSQL >> MongoDB

Získejte filtrovaný počet prvků v poli z $lookup spolu s celým dokumentem

Anotace pro ty, kteří hledají - Cizí hrabě

O něco lepší, než bylo původně zodpovězeno, je skutečně použít novější formu $lookup z MongoDB 3.6. To může ve skutečnosti provést "počítání" ve výrazu "sub-pipeline" na rozdíl od vracení "pole" pro následné filtrování a počítání nebo dokonce pomocí $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" }
  }}
])

Ne to, co požadovala původní otázka, ale část níže uvedené odpovědi v nyní nejoptimálnější podobě, jako samozřejmě výsledek $lookup pouze se sníží na „počet shodných položek“. místo "všechny odpovídající dokumenty".

Původní

Správný způsob, jak to udělat, je přidat "linkCount" do $group fázi a také $first v jakýchkoli dalších polích nadřazeného dokumentu, abyste získali „singulární“ formu, jako byl stav „před“ $unwind byl zpracován na poli, které bylo výsledkem $lookup :

Všechny podrobnosti

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

Vyrábí:

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

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

Produkuje

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

Důvod, proč to děláte tímto způsobem s $unwind a poté $match je to kvůli tomu, jak MongoDB ve skutečnosti zpracovává potrubí, když je vydáváno v tomto pořadí. Toto se stane s $lookup jak je demonstrováno "vysvětlit" výstup z operace:

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

Opouštím část s $group v tomto výstupu demonstrovat, že další dva stupně potrubí „zmizí“. Důvodem je, že byly „srolovány“ do $lookup fáze potrubí, jak je znázorněno. Takto se MongoDB ve skutečnosti vypořádává s možností, že limit BSON může být překročen výsledkem „spojení“ výsledků $lookup do pole nadřazeného dokumentu.

Operaci můžete střídavě napsat takto:

Všechny podrobnosti

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

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

Který má stejný výstup, ale "liší se" od prvního dotazu tím, že $filter zde se použije "po" VŠECHNY výsledky $lookup jsou vráceny do nového pole nadřazeného dokumentu.

Z hlediska výkonu je tedy ve skutečnosti efektivnější to udělat prvním způsobem a zároveň být přenositelný na možné velké sady výsledků „před filtrováním“, které by jinak porušilo limit 16 MB BSON.

Jako vedlejší poznámku pro ty, kteří mají zájem, v budoucích vydáních MongoDB (pravděpodobně 3.6 a vyšší) můžete použít $replaceRoot místo $addFields s použitím nového $mergeObjects provozovatel potrubí. Výhodou je, že jako "blok" můžeme prohlásit "filtrovaný" obsah jako proměnná prostřednictvím $let , což znamená, že nemusíte psát stejný $filter "dvakrát":

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

Nicméně nejlepší způsob, jak udělat takové "filtrované" $lookup operace je v tuto chvíli „stále“ pomocí $unwind pak $match vzor, ​​dokud nebudete moci poskytnout argumenty dotazu $ vyhledávání přímo.




  1. Mongodb - Map-Reduce - Kompletní data nejsou vrácena

  2. Dokážete najít jeden zápas jako první nebo poslední?

  3. Aby $elemMatch (projekce) vrátil všechny objekty, které odpovídají kritériím

  4. Proč má middleware nodejs-mongodb jinou syntaxi než mongo shell?