【问题标题】:Add a new field to all documents of a collection with the value from the document field into MongoDB (Mongoose) with records of 300K+将新字段添加到集合的所有文档中,将文档字段中的值添加到 MongoDB (Mongoose) 中,记录为 300K+
【发布时间】:2021-12-08 22:56:55
【问题描述】:

我很难将另一个字段添加到 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 字段中。

我能够使用来自 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 我已经编辑了我的问题以包含我正在使用的聚合,谢谢。

标签: javascript node.js mongodb mongoose nosql


【解决方案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 分钟,与我最初的实施相比,我需要一天时间才能更新数万个文档,这是一个显着的改进。

【讨论】:

    猜你喜欢
    • 2020-07-04
    • 2021-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-01
    • 2011-12-04
    • 2018-08-09
    相关资源
    最近更新 更多