Anmerkung für Suchende - Ausländische Zählung
Ein bisschen besser als ursprünglich beantwortet, ist es, tatsächlich die neuere Form von $nachschlagen
von MongoDB 3.6. Dies kann tatsächlich das „Zählen“ innerhalb des „Sub-Pipeline“-Ausdrucks übernehmen, anstatt ein „Array“ zum anschließenden Filtern und Zählen zurückzugeben oder sogar $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" }
}}
])
Nicht das, wonach die ursprüngliche Frage gefragt wurde, aber ein Teil der folgenden Antwort in der jetzt optimalsten Form, wie natürlich das Ergebnis von $lookup
wird nur auf die "übereinstimmende Anzahl" reduziert statt "alle übereinstimmenden Dokumente".
Original
Der richtige Weg, dies zu tun, wäre, den "linkCount"
hinzuzufügen zu $group
Stufe sowie ein $first
auf allen zusätzlichen Feldern des übergeordneten Dokuments, um die "Singular"-Form zu erhalten, wie es der Zustand "vor" dem $unwind
wurde auf dem Array verarbeitet, das das Ergebnis von $lookup
:
Alle Details
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", [] ]
}
}
}
}}
])
Erzeugt:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Gruppieren nach 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", [] ]
}
}
}
}}
])
Produziert
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
Der Grund, warum Sie es auf diese Weise mit einem $unwind
und dann ein $match
liegt daran, wie MongoDB die Pipeline tatsächlich handhabt, wenn sie in dieser Reihenfolge ausgegeben wird. Das passiert mit $lookup
wie der "explain"
demonstriert Ausgabe der Operation:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Ich verlasse den Teil mit $group
in dieser Ausgabe, um zu demonstrieren, dass die anderen beiden Pipeline-Stufen "verschwinden". Dies liegt daran, dass sie in den "aufgerollt" wurden $nachschlagen
Pipeline-Stufe wie abgebildet. Auf diese Weise geht MongoDB tatsächlich mit der Möglichkeit um, dass das BSON-Limit durch das Ergebnis der "Verknüpfung" von Ergebnissen von $lookup
in ein Array des übergeordneten Dokuments.
Alternativ können Sie die Operation auch so schreiben:
Alle Details
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", [] ] } }
}
}
}
}}
])
Nach partId gruppieren
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" }
}}
])
Welche die gleiche Ausgabe hat, sich aber von der ersten Abfrage dadurch "unterscheidet", dass $filter
hier wird "after" ALL angewendet Ergebnisse von $lookup
werden in das neue Array des übergeordneten Dokuments zurückgegeben.
In Bezug auf die Leistung ist es also tatsächlich effektiver, es auf die erste Art zu tun und auf mögliche große Ergebnismengen "vor dem Filtern" portierbar zu sein, die andernfalls die 16-MB-BSON-Grenze überschreiten würden.
Als Randnotiz für diejenigen, die daran interessiert sind, können Sie in zukünftigen Versionen von MongoDB (vermutlich 3.6 und höher) $replaceRoot
anstelle eines $addFields
mit Verwendung der neuen $mergeObjects
Pipeline-Betreiber. Der Vorteil davon ist, dass wir als "Block" den "gefiltert"
deklarieren können Inhalt als Variable über $let
, was bedeutet, dass Sie nicht denselben $filter schreiben müssen
"zweimal":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Nichtsdestotrotz ist der beste Weg, solche "gefilterten" $lookup
Der Betrieb erfolgt derzeit „noch“ unter Verwendung von $unwind
dann $match
Muster, bis Sie $ Abfrageargumente bereitstellen können nachschlagen
direkt.