Можете ли вы искать несколько документов по идентификатору в одном конвейере агрегации? - PullRequest
0 голосов
/ 17 мая 2018

Я пишу детальное представление API пользователя и хочу добавить флаг is_following, чтобы указать, следует ли за аутентифицированным пользователем API ( пользователь A ) пользователь, профиль которого он просматривает ( пользователь B ).

На момент запроса у меня был только аутентифицированный пользователь API _id. Я мог бы получить поле following для аутентифицированного пользователя API в одном запросе, а затем продолжить делать второй запрос для пользователя, которого он заинтересован в просмотре, но я предполагаю, что было бы более эффективно передать эту работу в базу данных. в одной агрегации (если возможно).

Допустим, у меня есть следующая схема для коллекции users:

[
    {
        _id: 1,
        name: 'David',
        following: [2, 3] // David follows Sam and Lucy
    },
    {
        _id: 2,
        name: 'Sam',
        following: [1] // Sam only follows David
    },
    {
        _id: 3,
        name: 'Lucy',
        following: [1, 2] // Lucy follows David and Sam
    },
]

Мне интересно, можно ли написать агрегацию так, как я сейчас думаю:

  1. Соответствует аутентифицированному пользователю API ( пользователь A ) по _id
  2. Сохраните массив following для пользователя A каким-либо образом в конвейере агрегации для последующего использования с пользователем, которого мы хотим просмотреть
  3. Повторное сопоставление для другого пользователя, пользователя, пользователя A , заинтересованного в просмотре ( пользователь B ), по его _id
  4. Используя проекцию, добавьте новое логическое поле is_following, которое представляет, находится ли пользователь B _id в пределах аутентифицированного пользователя API ( пользователь A ) following массив, который мы сохранили ранее. Т.е. вошедший в систему пользователь следует за пользователем, профиль которого он просматривает.

Это шаги 2 и 3, в которых я не уверен, я не уверен, может ли быть достигнут такой подход получения двух документов с разными $match критериями в рамках одного запроса агрегации.

Редактировать: добавление ожидаемого результата

Если аутентифицированный пользователь ( пользователь A ) был _id 2, а просматриваемый пользователь, пользователь B , был _id 1:

{
    _id: 1,
    name: 'David',
    following: [2, 3],
    is_following: true
}

Если аутентифицированный пользователь ( пользователь A ) был _id 2, а просматриваемый пользователь пользователь B был _id 3:

{
    _id: 3,
    name: 'Lucy',
    following: [1, 2],
    is_following: false
}

1 Ответ

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

Не на 100% ясно в вашем вопросе, но вы, похоже, ищете простые $lookup по тем же данным со сравнением итоговых пользователей.Вы, вероятно, действительно хотите этого только для одного пользователя за раз, поэтому $match для этого пользователя, как правило, является здесь «опцией».

В ней не так много, кроместандартный $lookup результат с последующим использованием $map для сравнения с «родителем» _id и добавления нового свойства в массив.

В идеале с MongoDB 3.6 вы можете применять $mergeObjects:

Person.aggregate([
  // { "$match": { "_id": 3 } },
  { "$lookup": {
    "from": Person.collection.name,
    "localField": "following",
    "foreignField": "_id",
    "as": "following"
  }},
  { "$addFields": {
    "following": {
      "$map": {
        "input": "$following",
        "in": {
          "$mergeObjects": [
            "$$this",
            { "is_following": {
              "$in": [ "$_id", "$$this.following" ]
            }}
          ]
        }
      }
    }
  }}
])

А для более ранних версий вам просто нужно назвать поля "явно" в пределах $map, которые вы хотите вернуть:

Person.aggregate([
  // { "$match": { "_id": 3 } },
  { "$lookup": {
    "from": Person.collection.name,
    "localField": "following",
    "foreignField": "_id",
    "as": "following"
  }},
  { "$project": {
    "_id": 1,
    "name": 1,
    "following": {
      "$map": {
        "input": "$following",
        "in": {
          "_id": "$$this._id",
          "name": "$$this.name",
          "following": "$$this.following",
          "is_following": { "$setIsSubset": [["$_id"], "$$this.following"] }
        }
      }
    }
  }}
])

Основы таковы, что каждый элемент проверяется $map. Вы можете сравнить их собственный массив "followers" и текущийпользовательское _id значение.Для сравнения можно использовать $in или $setIsSubset.Один сравнивает значение с массивом, а другие - с двумя «массивами».

Альтернативно, когда у вас даже нет доступа к $lookup, вы можете .populate(), а затем просто-map "содержимое массива во многом таким же образом:

let people = await Person.find().lean().populate('following');

people = people.map(p => ({ 
  ...p,
  following: p.following.map(f => ({
    ...f,
    is_following: f.following.some(e => e._id.equals(p._id))
  }))
}));

Использование Array.some() с методом ObjectID.equals() для сравнения.Обратите внимание, что Array.includes() не может применяться здесь без переназначения значений ObjectId в строку из-за того, как работает «сравнение объектов».

Все формы здесь приводят к одному и тому же результату:

{
        "_id" : 1,
        "name" : "David",
        "following" : [
                {
                        "_id" : 2,
                        "name" : "Sam",
                        "following" : [
                                1
                        ],
                        "is_following" : true
                },
                {
                        "_id" : 3,
                        "name" : "Lucy",
                        "following" : [
                                1,
                                2
                        ],
                        "is_following" : true
                }
        ]
}
{
        "_id" : 2,
        "name" : "Sam",
        "following" : [
                {
                        "_id" : 1,
                        "name" : "David",
                        "following" : [
                                2,
                                3
                        ],
                        "is_following" : true
                }
        ]
}
{
        "_id" : 3,
        "name" : "Lucy",
        "following" : [
                {
                        "_id" : 1,
                        "name" : "David",
                        "following" : [
                                2,
                                3
                        ],
                        "is_following" : true
                },
                {
                        "_id" : 2,
                        "name" : "Sam",
                        "following" : [
                                1
                        ],
                        "is_following" : false
                }
        ]
}
...