Если у вас есть MongoDB 3.6, тогда вы можете использовать $lookup
с «под-конвейером»
User.aggregate([
{ "$lookup": {
"from": Friend.collection.name,
"let": { "friends": "$friends" },
"pipeline": [
{ "$match": {
"recipient": ObjectId("5afaab572c4ec049aeb0bcba"),
"$expr": { "$in": [ "$_id", "$$friends" ] }
}},
{ "$project": { "status": 1 } }
],
"as": "friends"
}},
{ "$addFields": {
"friends": {
"$map": {
"input": "$friends",
"in": "$$this._id"
}
},
"friendsStatus": {
"$ifNull": [ { "$min": "$friends.status" }, 0 ]
}
}}
])
Для более ранних версий идеально использовать $unwind
, чтобы гарантировать, что вы не нарушите BSON Limit :
User.aggregate([
{ "$lookup": {
"from": Friend.collection.name,
"localField": "friends",
"foreignField": "_id",
"as": "friends"
}},
{ "$unwind": { "path": "$friends", "preserveNullAndEmptyArrays": true } },
{ "$match": {
"$or": [
{ "friends.recipient": ObjectId("5afaab572c4ec049aeb0bcba") },
{ "friends": null }
]
}},
{ "$group": {
"_id": "$_id",
"firstName": { "$first": "$firstName" },
"friends": { "$push": "$friends._id" },
"friendsStatus": {
"$min": {
"$ifNull": ["$friends.status",0]
}
}
}}
])
Здесь есть «одно отличие» от самой оптимальной формы в том, что оптимизация конвейера на самом деле не «сворачивает» $match
условие в $lookup
само:
{
"$lookup" : {
"from" : "friends",
"as" : "friends",
"localField" : "friends",
"foreignField" : "_id",
"unwinding" : {
"preserveNullAndEmptyArrays" : true
}
}
},
{
"$match" : { // <-- outside will preserved array
Поскольку параметр preserveNullAndEmptyArrays
равен true
, то действие "1030 *" полностью оптимизировано , когда условие фактически будет применено к внешней коллекции "до" . не бывает.
Таким образом, единственная цель unwinding
здесь состоит исключительно в том, чтобы избежать того, что обычно является целевым «массивом» из результата $lookup
, в результате чего родительский документ выходит за пределы BSON Limit. Дополнительные условия $match
затем применяются «после» этого этапа. Значение по умолчанию $unwind
без опции предполагает false
для сохранения, и вместо этого добавляется условие matching
. Это, конечно, приведет к тому, что документы без иностранных совпадений будут исключены.
И не очень рекомендуется из-за этого BSON Limit , но есть также применение $filter
к результирующему массиву $lookup
:
User.aggregate([
{ "$lookup": {
"from": Friend.collection.name,
"localField": "friends",
"foreignField": "_id",
"as": "friends"
}},
{ "$addFields": {
"friends": {
"$map": {
"input": {
"$filter": {
"input": "$friends",
"cond": {
"$eq": [
"$$this.recipient",
ObjectId("5afaab572c4ec049aeb0bcba")
]
}
}
},
"in": "$$this._id"
}
},
"friendsStatus": {
"$ifNull": [
{ "$min": {
"$map": {
"input": {
"$filter": {
"input": "$friends",
"cond": {
"$eq": [
"$$this.recipient",
ObjectId("5afaab572c4ec049aeb0bcba")
]
}
}
},
"in": "$$this.status"
}
}},
0
]
}
}}
])
В любом случае мы в основном добавляем «дополнительное условие» к соединению, которое заключается не только в непосредственно связанном поле, но и с дополнительным ограничением запрашиваемого значения ObjectId
для "recipient"
.
Не совсем уверен, что вы ожидаете от "friendsStatus"
, так как результатом является массив и может быть более одного (насколько я знаю), и поэтому просто применяйте $min
здесь извлечь одно значение из массива в любом случае.
Управляющим условием в каждом случае является $ifNull
, которое применяется там, где в выходном массиве "friends"
нет ничего для извлечения, а затем вы просто возвращаете результат 0
, где это тот случай.
Все выводят одно и то же:
{
"_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
"firstName" : "bruce",
"friends" : [
ObjectId("5afd1c42af18d985a06ac306")
],
"friendsStatus" : 2
}
{
"_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
"firstName" : "clerk",
"friends" : [ ],
"friendsStatus" : 0
}