我很难将另一个字段添加到user
集合的所有记录中,其中包含来自每个文档的值。我熟悉使用$set
和db.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分钟,这与我最初的实现相比是一个显著的改进,我花了一天的时间来更新数万个文档。