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

11

考虑到下面的文档,我该如何将“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控制台中,我尝试了命令,控制台显示"ok",但实际上没有任何更新。

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

我也尝试了这个方法,但是出现了错误:"不能使用(checkouts.techId1的checkouts部分)来遍历元素"

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

在mongoose中,我尝试了以下操作。

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) {});

提前致谢。

2个回答

13
你接近成功了,但还有几个问题。在使用占位符操作符时,你不能使用$rename命令,而是需要使用$set命令设置新名称并使用$unset命令删除旧名称。但是这里还有另一个限制,因为它们都属于"checkouts"作为父路径,所以你不能同时执行两个操作。
你问题中的另一条核心线索是“遍历元素”,但这是在一次更新“所有”数组元素时无法做到的事情。嗯,不安全,可能会覆盖正在输入的新数据。
你需要做的是“迭代”每个文档,并同样迭代每个数组成员,以“安全”更新。你不能仅仅迭代文档并带着修改后的整个数组“保存”回去。特别是在其他任何东西正在使用数据的情况下,这是行不通的。
如果你能,我个人会在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
@fpena06 我注意到我漏掉了一个检查,即要添加一个检查,以确保正在处理的数组元素上存在“techId1”元素。假设它总是存在可能没问题,但为了安全起见,我在包装数组迭代块的代码中添加了条件检查。 - Blakes Seven

0

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

首先,创建一个aggregate,它(1) $match 匹配具有checkouts数组字段的文档,并且每个子文档的键之一为techId1。(2) $unwind 展开checkouts字段(将输入文档中的数组字段拆分为每个元素输出一个文档),(3) 添加techId字段(使用$addFields),(4) $unset 旧的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"
        }
    }
])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接