gpt4 book ai didi

mongodb - 将ObjectId与$ graphLookup的字符串匹配

转载 作者:可可西里 更新时间:2023-11-01 09:58:35 24 4
gpt4 key购买 nike

我试着运行一个$graphLookup类似于下面的打印演示:
$graphLookup sample
目标是,给定一个特定的记录(注释$match),通过immediateAncestors属性检索它的完整“路径”。如你所见,它没有发生。
我在这里介绍了$convert来处理collection中的_id作为string,相信可以与_id记录列表中的immediateAncestors进行“匹配”(这是一个string)。
所以,我用不同的数据进行了另一个测试(没有涉及到ObjectIds):

db.nodos.insert({"id":5,"name":"cinco","children":[{"id":4}]})
db.nodos.insert({"id":4,"name":"quatro","ancestors":[{"id":5}],"children":[{"id":3}]})
db.nodos.insert({"id":6,"name":"seis","children":[{"id":3}]})
db.nodos.insert({"id":1,"name":"um","children":[{"id":2}]})
db.nodos.insert({"id":2,"name":"dois","ancestors":[{"id":1}],"children":[{"id":3}]})
db.nodos.insert({"id":3,"name":"três","ancestors":[{"id":2},{"id":4},{"id":6}]})
db.nodos.insert({"id":7,"name":"sete","children":[{"id":5}]})

以及查询:
db.nodos.aggregate( [
{ $match: { "id": 3 } },
{ $graphLookup: {
from: "nodos",
startWith: "$ancestors.id",
connectFromField: "ancestors.id",
connectToField: "id",
as: "ANCESTORS_FROM_BEGINNING"
}
},
{ $project: {
"name": 1,
"id": 1,
"ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING.id"
}
}
] )

…哪个输出了我所期望的(这五个记录直接或间接地与 id3的记录相关):
{
"_id" : ObjectId("5afe270fb4719112b613f1b4"),
"id" : 3.0,
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [
1.0,
4.0,
6.0,
5.0,
2.0
]
}

问题是:有一种方法可以实现我在开头提到的目标吗?
我在运行Mongo 3.7.9(来自官方Docker)
提前谢谢!

最佳答案

您目前正在使用MongoDB的开发版本,该版本具有一些功能,这些功能预计将作为官方版本随MongoDB 4.0一起发布。请注意,在最终版本发布之前,某些功能可能会发生更改,因此生产代码在提交之前应该知道这一点。
为什么$convert在这里失败
解释这一点的最好方法可能是查看更改后的示例,但将其替换为ObjectId_id值和数组下的“strings”:

{
"_id" : ObjectId("5afe5763419503c46544e272"),
"name" : "cinco",
"children" : [ { "_id" : "5afe5763419503c46544e273" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e273"),
"name" : "quatro",
"ancestors" : [ { "_id" : "5afe5763419503c46544e272" } ],
"children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e274"),
"name" : "seis",
"children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e275"),
"name" : "um",
"children" : [ { "_id" : "5afe5763419503c46544e276" } ]
}
{
"_id" : ObjectId("5afe5763419503c46544e276"),
"name" : "dois",
"ancestors" : [ { "_id" : "5afe5763419503c46544e275" } ],
"children" : [ { "_id" : "5afe5763419503c46544e277" } ]
},
{
"_id" : ObjectId("5afe5763419503c46544e277"),
"name" : "três",
"ancestors" : [
{ "_id" : "5afe5763419503c46544e273" },
{ "_id" : "5afe5763419503c46544e274" },
{ "_id" : "5afe5763419503c46544e276" }
]
},
{
"_id" : ObjectId("5afe5764419503c46544e278"),
"name" : "sete",
"children" : [ { "_id" : "5afe5763419503c46544e272" } ]
}

这应该能给你一个大致的模拟。
在进入 _id阶段之前,您试图通过 $project$graphLookup值转换为“字符串”。失败的原因是,当您在“该管道”中执行初始 $project“时,问题是 $graphLookup选项中 "from"的源仍然是未更改的集合,因此您无法获得有关后续“查找”迭代的正确详细信息。
db.strcoll.aggregate([
{ "$match": { "name": "três" } },
{ "$addFields": {
"_id": { "$toString": "$_id" }
}},
{ "$graphLookup": {
"from": "strcoll",
"startWith": "$ancestors._id",
"connectFromField": "ancestors._id",
"connectToField": "_id",
"as": "ANCESTORS_FROM_BEGINNING"
}},
{ "$project": {
"name": 1,
"ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
}}
])

与“查找”不匹配,因此:
{
"_id" : "5afe5763419503c46544e277",
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [ ]
}

“修补”问题
然而,这是核心问题,而不是 $convert的失败,也不是别名本身。为了使它实际工作,我们可以创建一个 "view",它将自己作为一个集合呈现,以便输入。
我将以另一种方式来执行此操作,并通过 ObjectId将“字符串”转换为 $toObjectId
db.createView("idview","strcoll",[
{ "$addFields": {
"ancestors": {
"$ifNull": [
{ "$map": {
"input": "$ancestors",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
},
"children": {
"$ifNull": [
{ "$map": {
"input": "$children",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
}
}}
])

但是,使用 "view"意味着数据与转换后的值一致。因此,使用视图进行以下聚合:
db.idview.aggregate([
{ "$match": { "name": "três" } },
{ "$graphLookup": {
"from": "idview",
"startWith": "$ancestors._id",
"connectFromField": "ancestors._id",
"connectToField": "_id",
"as": "ANCESTORS_FROM_BEGINNING"
}},
{ "$project": {
"name": 1,
"ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
}}
])

返回预期输出:
{
"_id" : ObjectId("5afe5763419503c46544e277"),
"name" : "três",
"ANCESTORS_FROM_BEGINNING" : [
ObjectId("5afe5763419503c46544e275"),
ObjectId("5afe5763419503c46544e273"),
ObjectId("5afe5763419503c46544e274"),
ObjectId("5afe5763419503c46544e276"),
ObjectId("5afe5763419503c46544e272")
]
}

解决问题
尽管如此,这里真正的问题是你有一些数据“看起来像”一个 ObjectId值,实际上是有效的 ObjectId,但是它被记录为一个“字符串”。一切正常工作的基本问题是,两个“类型”不相同,这会导致在尝试“连接”时出现相等不匹配。
因此,真正的修正仍然是一如既往的,即遍历数据并修正它,使“字符串”实际上也是 ObjectId值。然后,它们将与要引用的 _id键匹配,并且您将节省相当多的存储空间,因为 ObjectId比十六进制字符的字符串表示占用更少的存储空间。
使用MongoDB 4.0方法,您“可以”实际使用 "$toObjectId"来编写一个新的集合,这与我们之前创建的“视图”非常类似:
db.strcoll.aggregate([
{ "$addFields": {
"ancestors": {
"$ifNull": [
{ "$map": {
"input": "$ancestors",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
},
"children": {
"$ifNull": [
{ "$map": {
"input": "$children",
"in": { "_id": { "$toObjectId": "$$this._id" } }
}},
"$$REMOVE"
]
}
}}
{ "$out": "fixedcol" }
])

当然,如果您“需要”保持相同的集合,那么传统的“循环和更新”将保持不变:
var updates = [];

db.strcoll.find().forEach(doc => {
var update = { '$set': {} };

if ( doc.hasOwnProperty('children') )
update.$set.children = doc.children.map(e => ({ _id: new ObjectId(e._id) }));
if ( doc.hasOwnProperty('ancestors') )
update.$set.ancestors = doc.ancestors.map(e => ({ _id: new ObjectId(e._id) }));

updates.push({
"updateOne": {
"filter": { "_id": doc._id },
update
}
});

if ( updates.length > 1000 ) {
db.strcoll.bulkWrite(updates);
updates = [];
}

})

if ( updates.length > 0 ) {
db.strcoll.bulkWrite(updates);
updates = [];
}

这实际上有点像“大锤”,因为一次就覆盖了整个数组。对于生产环境来说,这不是一个好主意,但对于本练习来说,这已经足够作为演示了。
结论
因此,虽然MongoDB 4.0将添加这些“casting”特性,这些特性确实非常有用,但它们的实际意图并不是针对这样的情况。事实上,与大多数其他可能的用途相比,它们在使用聚合管道“转换”到新集合时更有用。
虽然我们“可以”创建一个“视图”,它可以转换数据类型,使 $lookup$graphLookup这样的东西在实际收集数据不同的地方工作,但这实际上只是解决实际问题的一个“创可贴”,因为数据类型真的不应该不同,而且事实上应该永久转换。
使用“视图”实际上意味着每次访问“集合”(实际上是“视图”)时,都需要有效地运行用于构建的聚合管道,这会产生实际的开销。
避免开销通常是一个设计目标,因此纠正这样的数据存储错误对于获得应用程序的实际性能是必不可少的,而不仅仅是使用“暴力”来降低速度。
一个更安全的“转换”脚本,它对每个数组元素应用“匹配”更新。这里的代码需要nodejs v10.x和最新版本的mongodb节点驱动程序3.1.x:
const { MongoClient, ObjectID: ObjectId } = require('mongodb');
const EJSON = require('mongodb-extended-json');

const uri = 'mongodb://localhost/';

const log = data => console.log(EJSON.stringify(data, undefined, 2));

(async function() {

try {

const client = await MongoClient.connect(uri);
let db = client.db('test');
let coll = db.collection('strcoll');

let fields = ["ancestors", "children"];

let cursor = coll.find({
$or: fields.map(f => ({ [`${f}._id`]: { "$type": "string" } }))
}).project(fields.reduce((o,f) => ({ ...o, [f]: 1 }),{}));

let batch = [];

for await ( let { _id, ...doc } of cursor ) {

let $set = {};
let arrayFilters = [];

for ( const f of fields ) {
if ( doc.hasOwnProperty(f) ) {
$set = { ...$set,
...doc[f].reduce((o,{ _id },i) =>
({ ...o, [`${f}.$[${f.substr(0,1)}${i}]._id`]: ObjectId(_id) }),
{})
};

arrayFilters = [ ...arrayFilters,
...doc[f].map(({ _id },i) =>
({ [`${f.substr(0,1)}${i}._id`]: _id }))
];
}
}

if (arrayFilters.length > 0)
batch = [ ...batch,
{ updateOne: { filter: { _id }, update: { $set }, arrayFilters } }
];

if ( batch.length > 1000 ) {
let result = await coll.bulkWrite(batch);
batch = [];
}

}

if ( batch.length > 0 ) {
log({ batch });
let result = await coll.bulkWrite(batch);
log({ result });
}

await client.close();

} catch(e) {
console.error(e)
} finally {
process.exit()
}

})()

为七个文档生成并执行如下批量操作:
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e272"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e273"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e273"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e273"
}
},
"update": {
"$set": {
"ancestors.$[a0]._id": {
"$oid": "5afe5763419503c46544e272"
},
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e277"
}
}
},
"arrayFilters": [
{
"a0._id": "5afe5763419503c46544e272"
},
{
"c0._id": "5afe5763419503c46544e277"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e274"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e277"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e277"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e275"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e276"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e276"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e276"
}
},
"update": {
"$set": {
"ancestors.$[a0]._id": {
"$oid": "5afe5763419503c46544e275"
},
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e277"
}
}
},
"arrayFilters": [
{
"a0._id": "5afe5763419503c46544e275"
},
{
"c0._id": "5afe5763419503c46544e277"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5763419503c46544e277"
}
},
"update": {
"$set": {
"ancestors.$[a0]._id": {
"$oid": "5afe5763419503c46544e273"
},
"ancestors.$[a1]._id": {
"$oid": "5afe5763419503c46544e274"
},
"ancestors.$[a2]._id": {
"$oid": "5afe5763419503c46544e276"
}
}
},
"arrayFilters": [
{
"a0._id": "5afe5763419503c46544e273"
},
{
"a1._id": "5afe5763419503c46544e274"
},
{
"a2._id": "5afe5763419503c46544e276"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": {
"$oid": "5afe5764419503c46544e278"
}
},
"update": {
"$set": {
"children.$[c0]._id": {
"$oid": "5afe5763419503c46544e272"
}
}
},
"arrayFilters": [
{
"c0._id": "5afe5763419503c46544e272"
}
]
}
}

关于mongodb - 将ObjectId与$ graphLookup的字符串匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50402500/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com