将MongoDB中的_id字段从字符串转换为ObjectId进行连接

69

我有两个集合

  1. 用户

    {
       "_id" : ObjectId("584aac38686860d502929b8b"),
       "name" : "John"
    }
    
  2. 职位

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

我想根据role集合中的userId(在user集合中的_id)来连接这些集合。

我尝试了以下查询:

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

只要我将userId存储为ObjectId,就能得到预期的结果。但是当我的userId是字符串时,就没有结果了。 Ps: 我尝试过

foreignField: '_id'.valueOf()

foreignField: '_id'.toString()

。但是没有办法根据ObjectId字符串字段进行匹配/连接。

任何帮助将不胜感激。

4个回答

80
您可以使用 MongoDB 4.0 中的 $toObjectId 聚合,将字符串 id 转换为 ObjectId
db.role.aggregate([
  { "$lookup": {
    "from": "user",
    "let": { "userId": "$_id" },
    "pipeline": [
      { "$addFields": { "userId": { "$toObjectId": "$userId" }}},
      { "$match": { "$expr": { "$eq": [ "$userId", "$$userId" ] } } }
    ],
    "as": "output"
  }}
])
您可以使用来自MongoDB 4.0的聚合$toString,将ObjectId转换为String
db.role.aggregate([
  { "$addFields": { "userId": { "$toString": "$_id" }}},
  { "$lookup": {
    "from": "user",
    "localField": "userId",
    "foreignField": "userId",
    "as": "output"
  }}
])

3
请注意,这两个聚合运算符仅从4.0版本开始提供。 - Nech
2
@AnthonyWinzlet 这应该是现在被接受的答案。 - AJB
$toObjectId 的情况出现了错误。Gary Wild的回答修复了它。 - teuber789

50
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


嗨@Ashh,你在cond中使用了错误的括号,这个应该可以工作:https://mongoplayground.net/p/jKVt60tPtc6 - felix

50

我认为之前的回答在$toObjectId案例上存在错误。 let语句适用于调用函数aggregate的数据库集合(即'role'),而不是由“from”指向的集合(即'user')。

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"
  }}
])

之前回答中的 let 语句错误,你指出得很好。 - teuber789

2
例如,我们有两个集合:
  • 作者集合包含 {'_id': ObjectId(), name: 'John Smith'}
  • 消息集合包含 {'_id': ObjectId(), text: 'Message Text', authorId: 'stringId'}
  • 现在我们需要获取包含作者姓名和其他数据的消息。
那么,我们可以这样操作:
db.messages.aggregate([
  {
    $addFields: {
      '$authorObjectId': {$toObjectId: $authorId}  
    }
  },
  {
    $lookup: {
      from: 'authors',
      localField: '$authorObjectId',
      foreignField: '_id',
      as: 'author'
    }
  }
 ])

解释:

我们的聚合管道有两个步骤:

  • 第一步:我们向消息中添加一个额外的字段,其中包含将字符串authorId转换为ObjectId()的结果。
  • 第二步:我们使用该字段作为本地字段(在消息中)与外部字段'_id'(在作者中)进行比较。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接