Mongo如何使用DBRef进行$lookup

36

我有一个问题(/(ㄒoㄒ)/~~)。假设集合A为

{ 
    "_id" : ObjectId("582abcd85d2dfa67f44127e1"), 
    "bid" : [
        DBRef("B", ObjectId("582abcd85d2dfa67f44127e0")),
        DBRef("B", ObjectId("582abcd85d2dfa67f44127e1"))
    ]
}


和B集合:

{ 
    "_id" : ObjectId("582abcd85d2dfa67f44127e0"),  
    "status" : NumberInt(1), 
    "seq" : NumberInt(0)
},
{ 
    "_id" : ObjectId("582abcd85d2dfa67f44127e1"), 
    "status" : NumberInt(1), 
    "seq" : NumberInt(0)
} 


我不知道如何查找“bid”。我尝试过。

db.A.aggregate(
    [
        {$unwind: {path: "$bid"}},
        {$lookup: {from: "B", localField: "bid", foreignField: "_id", as: "bs"}},
    ]
) 



db.A.aggregate(
    [
        {$unwind: {path: "$bid"}},
        {$lookup: {from: "B", localField: "bid.$id", foreignField: "_id", as: "bs"}},
    ]
)


但它不起作用。有人可以帮助吗?谢谢。


你在文档中使用DBRef有充分的理由吗?文档中提到:除非你有充分的理由使用DBRefs,否则请使用手动引用(https://docs.mongodb.com/manual/reference/database-references/)。 - marmor
3个回答

33

实际上,另一个答案是错误的。您可以在聚合器中对DBref字段进行查找,而无需使用mapreduce。

解决方案

db.A.aggregate([
{
    $project: { 
        B_fk: {
          $map: { 
             input: { 
                  $map: {
                      input:"$bid",
                      in: {
                           $arrayElemAt: [{$objectToArray: "$$this"}, 1]
                      },
                  }
             },
             in: "$$this.v"}},
        }
}, 
{
    $lookup: {
        from:"B", 
        localField:"B_fk",
        foreignField:"_id", 
        as:"B"
    }
}
])

结果

{
    "_id" : ObjectId("59bb79df1e9c00162566f581"),
    "B_fk" : null,
    "B" : [ ]
},
{
    "_id" : ObjectId("582abcd85d2dfa67f44127e1"),
    "B_fk" : [
        ObjectId("582abcd85d2dfa67f44127e0"),
        ObjectId("582abcd85d2dfa67f44127e1")
    ],
    "B" : [
        {
            "_id" : ObjectId("582abcd85d2dfa67f44127e0"),
            "status" : NumberInt("1"),
            "seq" : NumberInt("0")
        }
    ]
}

简要说明

使用$map循环遍历DBRefs,将每个DBRef拆分成数组,仅保留$id字段,并通过$$this.v去除k:v格式,只保留ObjectId并删除其余部分。现在可以根据ObjectId进行查找。

逐步说明

聚合器中,DBRef BSON类型可以像对象一样处理,具有两个或三个字段(ref、id和db)。

如果执行以下操作:

db.A.aggregate([
    {
        $project: { 
            First_DBref_as_array: {$objectToArray:{$arrayElemAt:["$bid",0]}},
            Second_DBref_as_array: {$objectToArray:{$arrayElemAt:["$bid",1]}},
            }

    },

])

这是结果:

{
"_id" : ObjectId("582abcd85d2dfa67f44127e1"),
"First_DBref_as_array : [
    {
        "k" : "$ref",
        "v" : "B"
    },
    {
        "k" : "$id",
        "v" : ObjectId("582abcd85d2dfa67f44127e0")
    }
],
"Second_DBref_as_array" : [
    {
        "k" : "$ref",
        "v" : "B"
    },
    {
        "k" : "$id",
        "v" : ObjectId("582abcd85d2dfa67f44127e0")
    }
]
}

一旦您将dbref转换为数组,您可以通过仅查询索引1处的值来摆脱无用的字段,就像这样:

db.A.aggregate([
    {
        $project: { 
            First_DBref_as_array: {$arrayElemAt: [{$objectToArray:{$arrayElemAt:["$bid",0]}},1]},
            Second_DBref_as_array: {$arrayElemAt: [{$objectToArray:{$arrayElemAt:["$bid",0]}},1]},
            }

    },

])

结果:

{
    "_id" : ObjectId("582abcd85d2dfa67f44127e1"),
    "First_DBref_as_array" : {
        "k" : "$id",
        "v" : ObjectId("582abcd85d2dfa67f44127e0")
    },
    "Second_DBref_as_array" : {
        "k" : "$id",
        "v" : ObjectId("582abcd85d2dfa67f44127e0")
    }
}

然后你可以通过指向"$myvalue.v"来最终获得你想要的值,就像这样

db.A.aggregate([
    {
        $project: { 
            first_DBref_as_array: {$arrayElemAt: [{$objectToArray:{$arrayElemAt:["$bid",0]}},1]},
            second_DBref_as_array: {$arrayElemAt: [{$objectToArray:{$arrayElemAt:["$bid",0]}},1]},
            }

    },
    {
        $project: {
            first_DBref_as_ObjectId: "$first_DBref_as_array.v",
            second_DBref_as_ObjectId: "$second_DBref_as_array.v"
        }
    }

])

结果:

{
    "_id" : ObjectId("582abcd85d2dfa67f44127e1"),
    "first_DBref_as_ObjectId" : ObjectId("582abcd85d2dfa67f44127e0"),
    "second_DBref_as_ObjectId" : ObjectId("582abcd85d2dfa67f44127e0")
}

显然,在正常的管道中,你不需要所有这些冗余的步骤。通过使用嵌套的$map,你可以一次性得到相同的结果:

db.A.aggregate([
    {
        $project: { 
            B_fk: { $map : {input: { $map: {    input:"$bid",
                                    in: { $arrayElemAt: [{$objectToArray: "$$this"}, 1 ]}, } },
                            in: "$$this.v"}},

            }
    }, 

])

结果:

{
    "_id" : ObjectId("582abcd85d2dfa67f44127e1"),
    "B_fk" : [
        ObjectId("582abcd85d2dfa67f44127e0"),
        ObjectId("582abcd85d2dfa67f44127e1")
    ]
}

我希望解释足够清晰易懂,如果不是请随意提问。


你能详细说明一下这部分的意思吗?“然后使用$$this.v来摆脱k:v格式,只保留ObjectId并删除其余所有内容。” - Misi
1
正如我在答案的第二部分中详细说明的那样,一旦你将DBref转换为数组,它将看起来像这样: [{"k" : "$ref","v" : "B"},{"k" : "$id","v" : ObjectId("582abcd85d2dfa67f44127e0")}。由于你只想要objectId,所以你需要在$map操作中使用$$this.v而不是仅仅使用$$this来引用它。 - Olivier Maurel
@OlivierMaurel,你能否提供使用Spring MongoDB的解决方案吗?我似乎不太明白如何将其转换为Spring Boot MongoDB。我需要查找与ref和id关联的实际字段,而不是ObjectID。 - NotSoBrainy

11

如果有人在2021年来到这里,请注意:

MongoDB 4.3.3开始,第二个查询操作可以正常工作:

db.A.aggregate(
    [
        {$unwind: {path: "$bid"}},
        {$lookup: {from: "B", localField: "bid.$id", foreignField: "_id", as: "bs"}},
    ]
)

结果如下:

{
   "_id":ObjectId("582abcd85d2dfa67f44127e1"),
   "bid":DBRef("B", "ObjectId("582abcd85d2dfa67f44127e0")),
   "bs":[
      {
         "_id":ObjectId("582abcd85d2dfa67f44127e0")",
         "status":1,
         "seq":0
      }
   ]
}{
   "_id":ObjectId("582abcd85d2dfa67f44127e1"),
   "bid":DBRef("B", "ObjectId("582abcd85d2dfa67f44127e1")),
   "bs":[
      {
         "_id":ObjectId("582abcd85d2dfa67f44127e1"),
         "status":1,
         "seq":0
      }
   ]
}

更多信息请参见SERVER-14466


8
自mongoDB 3.4起,这是不可能的。在聚合管道中,除了$match阶段外,您不能使用DBRef。

我强烈建议您摆脱DBRef并切换到手动引用。但是,如果您确实需要保留DBRef,则可以使用以下(丑陋的)解决方案:

首先,创建一个名为“C”的新集合,其中使用mapReduce将DBRefs替换为其Ids:

db.A.mapReduce(
    function() {
        var key = this._id; 
        var value = [];  
        for ( var index = 0; index < this.bid.length; index++){
           value.push(this.bid[index].$id); 
        }
        emit(key, value); 
    },
    function(key,values) {
        return  values;
    },
    {
        "query": {},
        "out": "C" 
    }
)

然后,在新的 "C" 集合上运行聚合查询:

db.C.aggregate([
   {
      $unwind:"$value"
   },
   {
      $lookup:{
         from:"B",
         localField:"value",
         foreignField:"_id",
         as:"bs"
      }
   }
]);

输出:

    {
       "_id":ObjectId("582abcd85d2dfa67f44127e1"),
       "value":ObjectId("582abcd85d2dfa67f44127e0"),
       "bs":[
          {
             "_id":ObjectId("582abcd85d2dfa67f44127e0"),
             "status":1,
             "seq":0
          }
       ]
    }{
       "_id":ObjectId("582abcd85d2dfa67f44127e1"),
       "value":ObjectId("582abcd85d2dfa67f44127e1"),
       "bs":[
          {
             "_id":ObjectId("582abcd85d2dfa67f44127e1"),
             "status":1,
             "seq":0
          }
       ]
    }

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