добавить поля, где условие соответствует вложенному массиву - PullRequest
0 голосов
/ 17 мая 2018

У меня есть следующая users коллекция

[{
    "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
    "firstName" : "bruce",
    "friends" : [ ObjectId("5afd1c42af18d985a06ac306"),ObjectId("5afd257daf18d985a06ac6ac") ]
},
{
    "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
    "firstName" : "clerk",
    "friends" : [],
}]

и есть friends коллекция

[{
    "_id" : ObjectId("5afd1c42af18d985a06ac306"),
    "recipient" : ObjectId("5afaab572c4ec049aeb0bcba"),
    "requester" : ObjectId("5afadfdf08a7aa6f1a27d986"),
    "status" : 2,
},
{
    "_id" : ObjectId("5afd257daf18d985a06ac6ac"),
    "recipient" : ObjectId("5afadfdf08a7aa6f1a27d986"),
    "requester" : ObjectId("5afbfe21daf4b13ddde07dbe"),
    "status" : 1,
}]

предположим, что я вошел в систему с _id: "5afaab572c4ec049aeb0bcba", и это _id соответствует recipient из friends

Теперь мне нужно добавить поле friendsStatus, которое содержит status из friends коллекции ... И если не совпадает с любым recipient из массива, то его статус должен быть 0

Поэтому, когда я получу всех пользователей, мой вывод должен быть

[{
        "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
        "firstName" : "bruce",
        "friends" : [ ObjectId("5afd1c42af18d985a06ac306") ],
        "friendStatus": 2
},
{
        "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
        "firstName" : "clerk",
        "friends" : [],
        "friendStatus": 0
}]

Заранее спасибо !!!

1 Ответ

0 голосов
/ 17 мая 2018

Если у вас есть 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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...