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

Ottieni il conteggio filtrato degli elementi nell'array da $lookup insieme all'intero documento

Annotazione per chi cerca - Conte Esteri

Un po' meglio di come è stato risposto originariamente è utilizzare effettivamente la forma più recente di $ricerca da MongoDB 3.6. Questo può effettivamente eseguire il "conteggio" all'interno dell'espressione "sub-pipeline" invece di restituire un "array" per il successivo filtraggio e conteggio o persino utilizzando $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" }
  }}
])

Non quello che chiedeva la domanda originale, ma parte della risposta di seguito nella forma ora più ottimale, come ovviamente il risultato di $lookup è ridotto al "conteggio abbinato" solo invece di "tutti i documenti corrispondenti".

Originale

Il modo corretto per farlo sarebbe aggiungere il "linkCount" al $group stage e un $first su eventuali campi aggiuntivi del documento padre per ottenere il modulo "singolare" come era lo stato "prima" del $unwind è stato elaborato sull'array risultante da $lookup :

Tutti i dettagli

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

Produce:

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

Raggruppa per ID parte

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

Produce

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

Il motivo per cui lo fai in questo modo con un $unwind e poi un $match è a causa del modo in cui MongoDB gestisce effettivamente la pipeline quando emessa in quell'ordine. Questo è ciò che accade a $lookup come viene dimostrato il "explain" output dall'operazione:

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

Lascio la parte con $group in tale output per dimostrare che le altre due fasi della pipeline "scompaiono". Questo perché sono stati "arruolati" nel $cerca fase della pipeline come mostrato. Questo è infatti il ​​modo in cui MongoDB affronta la possibilità che il limite BSON possa essere superato dal risultato di "joining" risultati di $lookup in un array del documento padre.

In alternativa puoi scrivere l'operazione in questo modo:

Tutti i dettagli

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

Raggruppa per ID parte

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

Che ha lo stesso output ma "differisce" dalla prima query in quanto $filtro qui viene applicato "dopo" TUTTI risultati del $lookup vengono restituiti nel nuovo array del documento padre.

Quindi, in termini di prestazioni, è effettivamente più efficace farlo nel primo modo oltre ad essere portabile su possibili set di risultati di grandi dimensioni "prima del filtraggio" che altrimenti infrangerebbero il limite BSON di 16 MB.

Come nota a margine per coloro che sono interessati, nelle versioni future di MongoDB (presumibilmente 3.6 e successive) è possibile utilizzare $replaceRoot invece di un $addFields con l'utilizzo del nuovo $mergeObjects operatore di gasdotto. Il vantaggio di questo è come un "blocco", possiamo dichiarare il "filtrato" contenuto come variabile tramite $let , il che significa che non è necessario scrivere lo stesso $filter "due volte":

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

Tuttavia, il modo migliore per fare questo $lookup<"filtrato" /codice> operazioni è "fermo" in questo momento utilizzando $unwind quindi $match pattern, finché non potrai fornire argomenti di query a $ ricerca direttamente.