将新字段添加到集合的所有文档中,将文档字段中的值添加到 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 集合的所有记录中,每个文档都有一个值。我熟悉将$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 我已经编辑了我的问题以包含我正在使用的聚合,谢谢。 【参考方案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+的主要内容,如果未能解决你的问题,请参考以下文章

将新对象插入到 mongoose 中的子文档数组字段中

将新对象插入到 mongoose 中的子文档数组字段中

有没有办法在 couchdb 现有文档中添加新字段/值

如何使用 pymongo 将新的值数组附加到 mongodb 中的现有数组文档?

向 MongoDB 集合中的每个文档添加新字段

如何在swift ui中使用autoid将带有字段的新文档添加到firebase集合