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.