【问题标题】:Mongo how to $lookup with DBRefMongo 如何使用 DBRef 进行 $lookup
【发布时间】: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"}},
    ]
)


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

【问题讨论】:

标签: mongodb spring-data-mongodb


【解决方案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

    【讨论】:

      猜你喜欢
      • 2012-06-19
      • 2021-09-24
      • 2017-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-26
      • 2020-04-02
      • 1970-01-01
      相关资源
      最近更新 更多