Anotação para quem procura - Contagem Estrangeira
Um pouco melhor do que foi respondido originalmente é usar a forma mais recente de
$pesquisa
do MongoDB 3.6. Isso pode realmente fazer a "contagem" na expressão "sub-pipeline" em vez de retornar um "array" para filtragem e contagem subsequentes ou até mesmo 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" }
}}
])
Não o que a pergunta original estava pedindo, mas parte da resposta abaixo na forma mais otimizada, como é claro, o resultado de
$lookup
é reduzido para a "contagem de correspondências" somente em vez de "todos os documentos correspondentes". Original
A maneira correta de fazer isso seria adicionar o
"linkCount"
para o $group
palco, bem como um $first
em quaisquer campos adicionais do documento pai para obter o formulário "singular", como era o estado "antes" do $unwind
foi processado na matriz que foi o resultado de $lookup
:Todos os detalhes
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", [] ]
}
}
}
}}
])
Produz:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Agrupar por 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", [] ]
}
}
}
}}
])
Produz
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
A razão pela qual você faz isso com um
$unwind
e, em seguida, um $match
é por causa de como o MongoDB realmente lida com o pipeline quando emitido nessa ordem. Isso é o que acontece com o $lookup
como é demonstrado no "explain"
saída da operação: {
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Estou deixando a parte com
$group
nessa saída para demonstrar que os outros dois estágios do pipeline "desaparecem". Isso ocorre porque eles foram "agrupados" no $pesquisa
estágio de tubulação como mostrado. Na verdade, é assim que o MongoDB lida com a possibilidade de que o limite BSON possa ser excedido pelo resultado de "juntar" resultados de $lookup
em uma matriz do documento pai. Você pode escrever alternadamente a operação assim:
Todos os detalhes
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 tem a mesma saída, mas "difere" da primeira consulta, pois o
$filtro
aqui é aplicado "depois" de TODOS resultados do $lookup
são retornados para a nova matriz do documento pai. Portanto, em termos de desempenho, é realmente mais eficaz fazê-lo da primeira maneira, além de ser portátil para possíveis grandes conjuntos de resultados "antes da filtragem" que, de outra forma, quebrariam o limite BSON de 16 MB.
Como uma nota lateral para aqueles que estão interessados, em versões futuras do MongoDB (presumivelmente 3.6 ou superior) você pode usar
$replaceRoot
em vez de um $addFields
com o uso do novo $mergeObjects
operador de dutos. A vantagem disso é como um "bloco", podemos declarar o "filtrado"
conteúdo como uma variável via $let
, o que significa que você não precisa escrever o mesmo $filter
"duas vezes":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 entanto, a melhor maneira de fazer isso "filtrado"
$lookup
operações está "ainda" neste momento usando o $unwind
então $match
padrão, até que você possa fornecer argumentos de consulta para $ pesquisa
diretamente.