Mongo 如何使用 DBRef 进行 $lookup

Posted

技术标签:

【中文标题】Mongo 如何使用 DBRef 进行 $lookup【英文标题】:Mongo how to $lookup with DBRef 【发布时间】:2017-03-30 02:17:29 【问题描述】:

我有麻烦(/(ㄒ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)
 

我不知道如何 $lookup '出价'。我试过了

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 吗?来自文档:Unless you have a compelling reason to use DBRefs, use manual references instead (docs.mongodb.com/manual/reference/database-references) 【参考方案1】:

从 mongoDB 3.4 开始,这是不可能的。不能在聚合管道中使用 DBRef,$match 阶段除外。

我强烈建议您摆脱 DBRef 并切换到手动引用。但是,如果你真的需要保留 DBRef,这里有一个(丑陋的)解决方案:

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

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
          
       ]
    

【讨论】:

【参考方案2】:

实际上,另一个答案是错误的。可以在聚合器中查找 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 并删除所有其余部分。” ? 正如我在答案的第二部分中详述的那样,一旦将 DBref 转换为数组,它将如下所示:["k" : "$ref","v" : "B","k" : "$id","v" : ObjectId("582abcd85d2dfa67f44127e0")。由于您只需要 objectId,因此您需要在 $map 操作中使用 $$this.v 而不是仅使用 $$this 来引用它 @OlivierMaurel 你能在 spring mongo 中提供相同的解决方案吗?我似乎可以理解如何将其转换为 Spring Boot Mongodb。我需要查找与 ref 和 id 关联的实际字段,而不是 ObjectID。【参考方案3】:

以防万一有人在 2021 年来到这里:

从 MongoDB 4.3.3 开始,OP 的第二个查询确实有效:

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。

【讨论】:

以上是关于Mongo 如何使用 DBRef 进行 $lookup的主要内容,如果未能解决你的问题,请参考以下文章

Spring Mongo DB @DBREF

为啥 MongoDB 文档建议不要使用 DBREF?

如何在 Python 中解压嵌套的 DBRef?

如何取消引用mgo中的dbref?

如何在虚拟上实现 DBRef

使用 Mongoose / mongoose-dbref 使用和填充(真实的)DBRef 数组