在记录300K+的MongoDB (Mongoose)中,为集合的所有文档添加一个新的字段,该字段的值为document



我很难将另一个字段添加到user集合的所有记录中,其中包含来自每个文档的值。我熟悉使用$setdb.model.updateMany,$addFields和聚合管道,我过去都用过这两种方法来解决问题,在这种情况下,我必须在添加值之前执行一些逻辑/计算,这就是我的问题所在。

比如说,我有一个这样的模式:

{
"users": [
{
"wallets": {...},
"avatar": "",
"isVerified": false,
"suspended": false,
"country": "Nigeria",
"_id": "123",
"resetPasswordToken": "",
"email": "example@gmail.com",
"phone": "08012398743",
"name": "Agbakwuru Nnaemeka Kennedy ",
"role": "user",
},
{...}
}

我想添加一个新的字段phoneNumber,这将采取现有字段phone的值,但在添加之前,我想在它上运行一个逻辑,因为一些电话值有空格,其中大多数格式不正确,我想将国家代码添加到phone值,然后将其添加到新的phoneNumber字段。

我能够使用来自Mongoosedb.mode.aggregate方法的游标,使用$match过滤器,并使用聚合$addFields管道将字段添加到每个文档,这证明需要花费大量时间,我不得不停止操作,因为它需要太多时间来运行。

我愿意相信有更好的方法,请,我将感谢任何帮助。

编辑:

下面是我使用的聚合:

const userCursor = User.aggregate([{$match: {phone: {$exists: true}}}]);
for await (const doc of userCursor) {
await User.findByIdAndUpdate(doc._id, {$set: {
phoneNumber: convertPhoneNumber({phoneNumber: doc.phone.replace(/s+/g, "")})}
});
}

convertPhoneNumber是我在utils中定义的一个帮助器方法,用于在电话号码前添加国家/地区交易代码。

我会尝试直接在mongo命令行或Robo3T中运行这样的脚本:

db.getCollection("users").find({}).forEach( doc => {
doc.users.forEach( user => {
// do your logic here
let phoneNumber = "12345";
phoneNumber = "+007" + phoneNumber;
user.phoneNumber = phoneNumber;
})
db.users.save(doc);
})

对于300k以上的文档,它仍然需要一段时间,但请给它几分钟。

您可以使用$function并在数据库中调用该javascript代码。

这需要>=MongoDB 4.4

db.Users.update(
{phone: {$exists: true}},
[{$set: {phoneNumber:
{
"$function": {
"body": YOUR_convertPhoneNumber_FUNCTION_DEF,
"args": ["$phoneNumber"],
"lang": "js"
}
}])

如果convertPhoneNumber的代码可以在mongodb中使用聚合操作符编写,也可以避免javascript。

以上是一个管道更新,更新时可以使用所有聚合操作符。


编辑

如果mongoose有$function问题或者nodejs驱动程序方法有管道更新问题,你也可以这样做。

db.runCommand(
{
update: "yourCollectionName",
updates: [
{
q: {phone: {$exists: true}},
u: 
[{$set: {phoneNumber:
{
"$function": {
"body": YOUR_convertPhoneNumber_FUNCTION_DEF,
"args": ["$phoneNumber"],
"lang": "js"
}
}],
multi: true
}
],
ordered: false
}
)

您可以尝试批量操作,这将以1000个文档为批量更新集合:

var bulkOperations = [];
db.getCollection("users").find({}).forEach(doc => {
doc.users.forEach(user => {
user.phoneNumber = convertPhoneNumber({phoneNumber: user.phone.replace(/s+/g, "")});
})
bulkOperations.push({
updateOne: {
filter: { id: doc._id },
update: { $set: { users: doc.users } }
}
});
if (bulkOperations.length > 1000) {
db.getCollection("users").bulkWrite(bulkOperations, { ordered: false });
bulkOperations = [];
}
})
if (bulkOperations.length > 0) 
db.getCollection("users").bulkWrite(bulkOperations, { ordered: false });

在@Jeremy Thille的帮助下,我能够用下面的代码片段解决MongoDB Compassmongo命令行。

db.users.find({phone: {$exists: true}}).forEach( user => {
const phone = user.phone.replace(/s+/g, "");
const phoneNumber = `+234${phone.slice((phone.length - 10))}`;
db.users.updateOne({_id: user._id}, {$set: {phoneNumber}});
})

缺点是更新300K个文档只需要10-15分钟,这与我最初的实现相比是一个显著的改进,我花了一天的时间来更新数万个文档。

最新更新