重命名数组中的子文档字段

Posted

技术标签:

【中文标题】重命名数组中的子文档字段【英文标题】: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) );

提前致谢。

【问题讨论】:

【参考方案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"
        
    
])

【讨论】:

以上是关于重命名数组中的子文档字段的主要内容,如果未能解决你的问题,请参考以下文章

使用聚合重命名 MongoDB 中的字段

重命名数组 PHP 中的键

是否可以重命名 PyMongo 中 Mongo 查询输出中的字段?

更改/重命名我的 URL 中的子页面

对数据库中的表或字段重命名

如何重命名/替换 Kafka-connect SMT 结构中的字段?