MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Obtenez le nombre filtré d'éléments dans le tableau à partir de $lookup avec l'ensemble du document

Annotation pour ceux qui recherchent - Compte étranger

Un peu mieux que la réponse initiale consiste à utiliser la nouvelle forme de $lookup de MongoDB 3.6. Cela peut en fait faire le "comptage" dans l'expression "sous-pipeline" au lieu de renvoyer un "tableau" pour un filtrage et un comptage ultérieurs ou même d'utiliser $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" }
  }}
])

Pas ce que la question initiale demandait, mais une partie de la réponse ci-dessous sous la forme désormais la plus optimale, comme bien sûr le résultat de $lookup est réduit au "nombre de correspondances" uniquement au lieu de "tous les documents correspondants".

Original

La bonne façon de procéder serait d'ajouter le "linkCount" au $group stage ainsi qu'un $first sur tous les champs supplémentaires du document parent afin d'obtenir la forme "singulière" comme l'était l'état "avant" le $unwind a été traité sur le tableau qui était le résultat de $lookup :

Tous les détails

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

Produit :

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

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

Produit

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

La raison pour laquelle vous le faites de cette façon avec un $unwind puis un $match est dû à la façon dont MongoDB gère réellement le pipeline lorsqu'il est émis dans cet ordre. C'est ce qui arrive au $lookup comme le montre le "explain" résultat de l'opération :

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

Je quitte la partie avec $group dans cette sortie pour démontrer que les deux autres étapes du pipeline "disparaissent". C'est parce qu'ils ont été "regroupés" dans le $lookup étape de pipeline comme indiqué. C'est en fait ainsi que MongoDB gère la possibilité que la limite de BSON puisse être dépassée par le résultat de "rejoindre" les résultats de $lookup dans un tableau du document parent.

Vous pouvez alternativement écrire l'opération comme ceci :

Tous les détails

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

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

Qui a le même résultat mais "diffère" de la première requête en ce que le $filter ici est appliqué "après" TOUT résultats de la $lookup sont renvoyés dans le nouveau tableau du document parent.

Donc, en termes de performances, il est en fait plus efficace de le faire de la première manière tout en étant portable vers d'éventuels ensembles de résultats volumineux "avant le filtrage", ce qui autrement dépasserait la limite BSON de 16 Mo.

En remarque pour ceux qui sont intéressés, dans les futures versions de MongoDB (vraisemblablement 3.6 et plus), vous pouvez utiliser $replaceRoot au lieu d'un $addFields avec l'utilisation du nouveau $mergeObjects exploitant de pipeline. L'avantage de ceci est qu'en "bloc", on peut déclarer le "filtered" contenu en tant que variable via $let , ce qui signifie que vous n'avez pas besoin d'écrire le même $filter "deux fois":

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

Néanmoins, la meilleure façon de faire un tel "filtré" $lookup opérations est "toujours" en ce moment en utilisant le $unwind puis $match modèle, jusqu'à ce que vous puissiez fournir des arguments de requête à $lookup directement.