sql >> Base de Datos >  >> NoSQL >> MongoDB

Obtenga el recuento filtrado de elementos en la matriz de $búsqueda junto con todo el documento

Anotación para aquellos que buscan - Recuento extranjero

Un poco mejor de lo que se respondió originalmente es usar la forma más nueva de $lookup de MongoDB 3.6. En realidad, esto puede hacer el "conteo" dentro de la expresión "subcanalización" en lugar de devolver una "matriz" para el filtrado y el conteo posteriores o incluso usar $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" }
  }}
])

No es lo que pedía la pregunta original, sino parte de la respuesta a continuación en la forma ahora más óptima, como por supuesto el resultado de $lookup se reduce al "recuento coincidente" únicamente en lugar de "todos los documentos coincidentes".

Originales

La forma correcta de hacer esto sería agregar el "linkCount" al $group escenario así como un $first en cualquier campo adicional del documento principal para obtener el formulario "singular" como era el estado "antes" del $unwind se procesó en la matriz que fue el resultado de $lookup :

Todos los detalles

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
}

Agrupar por ID de pieza

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
}

La razón por la que lo hace de esta manera con un $unwind y luego un $match se debe a cómo MongoDB realmente maneja la canalización cuando se emite en ese orden. Esto es lo que sucede con $lookup como se demuestra en el "explain" salida de la operación:

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

Dejo la parte con $group en esa salida para demostrar que las otras dos etapas de canalización "desaparecen". Esto se debe a que se han "enrollado" en el $lookup etapa de tubería como se muestra. De hecho, así es como MongoDB trata la posibilidad de que el límite de BSON pueda ser excedido por el resultado de "unir" los resultados de $lookup en una matriz del documento principal.

Alternativamente, puede escribir la operación de esta manera:

Todos los detalles

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

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

Que tiene el mismo resultado pero "difiere" de la primera consulta en que $filter aquí se aplica "después" de TODO resultados de $lookup se devuelven a la nueva matriz del documento principal.

Entonces, en términos de rendimiento, en realidad es más efectivo hacerlo de la primera manera, además de ser portátil a posibles conjuntos de resultados grandes "antes del filtrado" que, de lo contrario, romperían el límite de BSON de 16 MB.

Como nota al margen para aquellos que estén interesados, en futuras versiones de MongoDB (presumiblemente 3.6 y posteriores) puede usar $replaceRoot en lugar de un $addFields con el uso del nuevo $mergeObjects operador de tubería La ventaja de esto es que como "bloque", podemos declarar el "filtered" contenido como una variable a través de $let , lo que significa que no necesita escribir el mismo $filter "dos veces":

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

No obstante, la mejor manera de hacer tal "filtrado" $lookup operaciones "todavía" en este momento usando el $unwind entonces $match patrón, hasta el momento en que pueda proporcionar argumentos de consulta a $lookup directamente.