将新字段添加到集合的所有文档中,将文档字段中的值添加到 MongoDB (Mongoose) 中,记录为 300K+
Posted
技术标签:
【中文标题】将新字段添加到集合的所有文档中,将文档字段中的值添加到 MongoDB (Mongoose) 中,记录为 300K+【英文标题】:Add a new field to all documents of a collection with the value from the document field into MongoDB (Mongoose) with records of 300K+ 【发布时间】:2021-12-08 22:56:55 【问题描述】:我很难将另一个字段添加到 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
字段中。
我能够使用来自 Mongoose db.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
是我在我的实用程序中定义的一种辅助方法,用于将国家/地区交易代码添加到电话号码。
【问题讨论】:
你能分享一下你正在使用的聚合吗?我们需要确定瓶颈所在,以便更好地帮助您。 @ray 我已经编辑了我的问题以包含我正在使用的聚合,谢谢。 【参考方案1】:我会尝试直接在 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 的文档仍需要一段时间,但请等待几分钟。
【讨论】:
谢谢@Jeremy,我会试试sn-p,我想问一下,你知道同样的逻辑可以在MongoDB Compass上运行吗? 您的回答帮助我解决了 Jeremy :) 哈,不客气。不幸的是,我从未使用过 Compass,我使用 Robo3T 作为 GUI【参考方案2】:您可以使用$function
并在数据库中调用该javascript 代码。
这需要 >=MongoDB 4.4
db.Users.update(
phone: $exists: true,
[$set: phoneNumber:
"$function":
"body": YOUR_convertPhoneNumber_FUNCTION_DEF,
"args": ["$phoneNumber"],
"lang": "js"
])
如果convertPhoneNumber的代码,可以用聚合运算符写在MongodBD中,你也可以避免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
)
【讨论】:
嗨@Takis_,我尝试使用$function
聚合管道,但出现此错误:Cannot run server-side javascript without the javascript engine enabled
,我读到了它,我需要联系 MongoDB 团队以启用它,但是我使用带有 DigitalOcean 的托管数据库,使用 DO,默认情况下禁用服务器端脚本。
如果你在做管道更新,你可以使用所有聚合运算符,你可能不需要javascript,我猜convertPhoneNumber可以用聚合运算符完成。
我有这个sn-p:await User.aggregate([ $addFields: phoneNumber: $function: body: function (phone) return convertPhoneNumber(phoneNumber: phone) , args: ["$phone"], lang: "js" ]);
我一运行就得到这个错误:MongoServerError: Cannot run server-side javascript
不,您不能这样做,您需要在定义中包含 convertPhoneNumber
的正文。即使允许javascript它也不起作用,mongodb看不到运行你的函数,所有这些代码都将在数据库中运行
谢谢,我收到了错误,我需要包含主体函数:The body function must be specified
,我将在主体函数中也包含convertPhoneNumber
逻辑,看看是否能解决它【参考方案3】:
你可以试试Bulk Operation,这样会批量更新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 );
【讨论】:
谢谢,我读到了 Bulk,但没有做太多,主要是因为我不确定它是如何工作的,我现在就试一试。【参考方案4】:在@Jeremy Thille 的回答here 的帮助下,我能够使用MongoDB Compass mongo
命令行和下面的sn-p 解决它。
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);
)
缺点是更新 30 万个文档需要大约 10 到 15 分钟,与我最初的实施相比,我需要一天时间才能更新数万个文档,这是一个显着的改进。
【讨论】:
以上是关于将新字段添加到集合的所有文档中,将文档字段中的值添加到 MongoDB (Mongoose) 中,记录为 300K+的主要内容,如果未能解决你的问题,请参考以下文章