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的主要内容,如果未能解决你的问题,请参考以下文章