Mongodb Присоединиться к полю _id от String to ObjectId

У меня есть две коллекции

  • Пользователь

             {
                 "_id" : ObjectId("584aac38686860d502929b8b"),
                 "name" : "John"
             }
    
  • Роль

     {
         "_id" : ObjectId("584aaca6686860d502929b8d"),
         "role" : "Admin",
         "userId" : "584aac38686860d502929b8b"  
     }
    

Я хочу присоединиться к этой коллекции на основе userId (в коллекции роль) - _id пользователе > коллекция).

Я попробовал следующий запрос:

db.role.aggregate(
{
   $lookup:
   {
       from: 'user',
       localField: 'userId',
       foreignField: '_id',
       as: 'output'
   }
}
);

Это дает мне ожидаемые результаты, пока я храню userId как ObjectId. Когда мой userId является строкой, результатов нет. Ps: Я пробовал

foreignField: '_id'.valueOf()

и

foreignField: '_id'.toString()

. Но не удастся совпадения/объединения на основе полей ObjectId.

Любая помощь будет оценена.

Ответ 1

Это невозможно с MongoDB 3.4. Эта функция уже была запрошена, но еще не реализована. Вот соответствующие билеты:

На данный момент вам придется хранить userId как ObjectId


РЕДАКТИРОВАТЬ

Предыдущие билеты были исправлены в MongoDB 4.0. Теперь вы можете достичь этого с помощью следующего запроса:

db.user.aggregate([
  {
    "$project": {
      "_id": {
        "$toString": "$_id"
      }
    }
  },
  {
    "$lookup": {
      "from": "role",
      "localField": "_id",
      "foreignField": "userId",
      "as": "role"
    }
  }
])

результат:

[
  {
    "_id": "584aac38686860d502929b8b",
    "role": [
      {
        "_id": ObjectId("584aaca6686860d502929b8d"),
        "role": "Admin",
        "userId": "584aac38686860d502929b8b"
      }
    ]
  }
]

попробуйте это онлайн: mongoplayground.net/p/JoLPVIb1OLS

Ответ 2

Вы можете использовать агрегацию $toObjectId из mongodb 4.0, которая преобразует id String в ObjectId

db.role.aggregate([
  { "$lookup": {
    "from": "user",
    "let": { "userId": "$_id" },
    "pipeline": [
      { "$addFields": { "userId": { "$toObjectId": "$userId" }}},
      { "$match": { "$expr": { "$eq": [ "$userId", "$$userId" ] } } }
    ],
    "as": "output"
  }}
])

Или вы можете использовать агрегацию $toString из mongodb 4.0, которая преобразует ObjectId в String

db.role.aggregate([
  { "$addFields": { "userId": { "$toString": "$_id" }}},
  { "$lookup": {
    "from": "user",
    "localField": "userId",
    "foreignField": "userId",
    "as": "output"
  }}
])

Ответ 3

Я думаю, что предыдущий ответ имеет ошибку в случае $ toObjectId. Оператор let применяется к коллекции БД, в которой вызывается aggregate функции (т.е. "Роль"), а не к коллекции, на которую указывает "from" (т.е. "Пользователь").

db.role.aggregate([
  { "$lookup": {
    "let": { "userObjId": { "$toObjectId": "$userId" } },
    "from": "user",
    "pipeline": [
      { "$match": { "$expr": { "$eq": [ "$_id", "$$userObjId" ] } } }
    ],
    "as": "userDetails"
  }}
])

Или же

db.role.aggregate([
  { "$project": { "userObjId": { "$toObjectId": "$userId" } } },
  { "$lookup": {
    "localField": "userObjId",
    "from": "user",
    "foreignField": "$_id",
    "as": "userDetails"
  }}
])

А также

db.user.aggregate([
  { "$project": { "userStrId": { "$toString": "$_id" }}},
  { "$lookup": {
    "localField": "userStrId",
    "from": "role",
    "foreignField": "userId",
    "as": "roleDetails"
  }}
])

Ответ 4

На самом деле это возможно в mongodb <4.0.

Вам необходимо добавить новое поле в документ Role, который представляет собой userId, преобразованный в ObjectID:

db.role.aggregate({
  { $addFields: { userobjectid: { $convert: { input: '$userId', to: 'objectId' } },
  { $lookup: 
    {
       from: 'user',
       localField: 'userobjectid',
       foreignField: '_id',
       as: 'output'
    }
  }
});