【问题标题】:Rename a sub-document field within an Array重命名数组中的子文档字段
【发布时间】:2015-10-19 16:57:10
【问题描述】:

考虑到下面的文档,我如何将“techId1”重命名为“techId”。我尝试了不同的方法,但无法让它发挥作用。

{
        "_id" : ObjectId("55840f49e0b"),
        "__v" : 0,
        "accessCard" : "123456789",
        "checkouts" : [ 
            {
                "user" : ObjectId("5571e7619f"),
                "_id" : ObjectId("55840f49e0bf"),
                "date" : ISODate("2015-06-19T12:45:52.339Z"),
                "techId1" : ObjectId("553d9cbcaf")
            }, 
            {
                "user" : ObjectId("5571e7619f15"),
                "_id" : ObjectId("55880e8ee0bf"),
                "date" : ISODate("2015-06-22T13:01:51.672Z"),
                "techId1" : ObjectId("55b7db39989")
            }
        ],
        "created" : ISODate("2015-06-19T12:47:05.422Z"),
        "date" : ISODate("2015-06-19T12:45:52.339Z"),
        "location" : ObjectId("55743c8ddbda"),
        "model" : "model1",
        "order" : ObjectId("55840f49e0bf"),
        "rid" : "987654321",
        "serialNumber" : "AHSJSHSKSK",
        "user" : ObjectId("5571e7619f1"),
        "techId" : ObjectId("55b7db399")
    }

在 mongo 控制台中,我试过了,但没有任何实际更新。

collection.update({"checkouts._id":ObjectId("55840f49e0b")},{ $rename: { "techId1": "techId" } });

我也试过这个,这给了我一个错误。 “不能使用部分(checkouts.techId1的结账)来遍历元素”

collection.update({"checkouts._id":ObjectId("55856609e0b")},{ $rename: { "checkouts.techId1": "checkouts.techId" } })

在猫鼬中,我尝试了以下方法。

collection.findByIdAndUpdate(id, { $rename: { "checkouts.techId1": "checkouts.techId" } }, function (err, data) {});

collection.update({'checkouts._id': n1._id}, { $rename: { "checkouts.$.techId1": "checkouts.$.techId" } }, function (err, data) {});

提前致谢。

【问题讨论】:

    标签: mongodb mongoose mongodb-query


    【解决方案1】:

    最后你很接近,但缺少一些东西。使用位置运算符时不能$rename,而是需要$set 新名称和$unset 旧名称。但是这里还有另一个限制,因为它们都属于“结帐”作为父路径,因为您不能同时执行这两个操作。

    您问题中的另一条核心线是“遍历元素”,这是您无法一次更新“所有”数组元素的一件事。好吧,这并不安全,而且无论如何都不会覆盖新数据。

    您需要做的是“迭代”每个文档并类似地迭代每个数组成员以“安全地”更新。您不能真正迭代文档并通过更改“保存”整个数组。当然不是在其他任何东西都在积极使用数据的情况下。

    如果可以的话,我个人会在 MongoDB shell 中运行这种操作,因为它是“一次性”(希望)的事情,这样可以节省编写其他 API 代码的开销。此外,我们在这里使用Bulk Operations API 以使其尽可能高效。使用 mongoose 需要更多的挖掘来实现,但仍然可以完成。但这里是 shell 列表:

    var bulk = db.collection.initializeOrderedBulkOp(),
        count = 0;
    
    db.collection.find({ "checkouts.techId1": { "$exists": true } }).forEach(function(doc) {
        doc.checkouts.forEach(function(checkout) {
            if ( checkout.hasOwnProperty("techId1") ) { 
                bulk.find({ "_id": doc._id, "checkouts._id": checkout._id }).updateOne({
                    "$set": { "checkouts.$.techId": checkout.techId1 }
                });
                bulk.find({ "_id": doc._id, "checkouts._id": checkout._id }).updateOne({
                    "$unset": { "checkouts.$.techId1": 1 }
                });
                count += 2;
    
                if ( count % 500 == 0 ) {
                    bulk.execute();
                    bulk = db.collection.initializeOrderedBulkOp();
                }
            }
        });
    });
    
    if ( count % 500 !== 0 ) 
        bulk.execute();
    

    由于 $set$unset 操作成对发生,我们将每次执行的总批处理大小保持在 1000 次操作,以降低客户端的内存使用量。

    循环只是查找要重命名的字段“存在”的文档,然后迭代每个文档的每个数组元素并提交两个更改。作为批量操作,这些操作在调用 .execute() 之前不会发送到服务器,每次调用也会返回一个响应。这样可以节省大量流量。

    如果您坚持使用 mongoose 进行编码。请注意,需要 .collection 访问器才能从核心驱动程序访问 Bulk API 方法,如下所示:

    var bulk = Model.collection.inititializeOrderedBulkOp();
    

    唯一发送到服务器的是.execute()方法,所以这是你唯一的执行回调:

    bulk.exectute(function(err,response) {
        // code body and async iterator callback here
    });
    

    并使用异步流控制代替.forEach()async.each

    另外,如果您这样做,请注意,作为不受 mongoose 管理的原始驱动程序方法,您不会获得与使用 mongoose 方法相同的数据库连接意识。除非您确定数据库连接已经建立,否则最好将此代码放在服务器连接的事件回调中:

    mongoose.connection.on("connect",function(err) {
        // body of code
    });
    

    但除此之外,这些是您真正需要的唯一真正的(除了调用语法)更改。

    【讨论】:

    • 这正是我想要的。非常感谢您的详细解释,阅读您的答案后绝对理解这个概念。
    • @fpena06 我注意到我错过的一件事是添加检查“techId1”元素是否确实存在于正在处理的数组元素上。假设它总是在那里可能是可以的,但以防万一我在包装数组迭代块的代码中添加了条件检查。
    【解决方案2】:

    这对我有用,我创建了这个查询来执行这个过程并分享它,(虽然我知道这不是最优化的方式):

    首先,创建一个aggregate,即(1)$match 具有checkouts 数组字段的文档,其中techId1 作为每个子文档的键之一。 (2)$unwindcheckouts 字段(从输入文档解构数组字段以输出每个元素的文档),(3)添加techId 字段(与$addFields),(4)@ 987654325@旧的techId1字段,(5)$group_id的文档再次将checkout子文档按其_id分组,以及(6)将这些聚合的结果写在一个temporal 集合(与$out)。

    const collection = 'yourCollection'
    
    db[collection].aggregate([
        {
            $match: {
                'checkouts.techId1': { '$exists': true }
            }
        },
        {
            $unwind: {
                path: '$checkouts'
            }
        },
        {
            $addFields: {
                'checkouts.techId': '$checkouts.techId1'
            }
        },
        {
            $project: {
                'checkouts.techId1': 0
            }
        },
        {
            $group: {
                '_id': '$_id',
                'checkouts': { $push: { 'techId': '$checkouts.techId' } }
            }
        },
        {
            $out: 'temporal'
        }
    ])
    

    然后,您可以从这个temporal 集合到$merge 将具有修改的checkouts 字段的文档再次聚合到您的原始集合。

    db.temporal.aggregate([
        {
            $merge: {
                into: collection,
                on: "_id",
                whenMatched:"merge",
                whenNotMatched: "insert"
            }
        }
    ])
    

    【讨论】:

      猜你喜欢
      • 2020-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-06
      • 2012-03-04
      • 2023-04-05
      • 1970-01-01
      相关资源
      最近更新 更多