使用另一个字段的值更新 MongoDB 字段

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3974985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 11:49:49  来源:igfitidea点击:

Update MongoDB field using value of another field

mongodbmongodb-queryaggregation-framework

提问by Chris Fulstow

In MongoDB, is it possible to update the value of a field using the value from another field? The equivalent SQL would be something like:

在 MongoDB 中,是否可以使用来自另一个字段的值更新字段的值?等效的 SQL 类似于:

UPDATE Person SET Name = FirstName + ' ' + LastName

And the MongoDB pseudo-code would be:

MongoDB 伪代码将是:

db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );

采纳答案by styvane

The best way to do this is in version 4.2+ which allows using of aggregation pipeline in the update document and the updateOne, updateManyor updatecollection method. Note that the latter has been deprecated in most if not all languages drivers.

最好的方法是在 4.2+ 版本中,它允许在更新文档和updateOne,updateManyupdate集合方法中使用聚合管道。请注意,后者在大多数语言驱动程序中已被弃用。

MongoDB 4.2+

MongoDB 4.2+

Version 4.2 also introduced the $setpipeline stage operator which is an alias for $addFields. I will use $sethere as it mapswith what we are trying to achieve.

4.2 版还引入了$set流水线阶段运算符,它是 $addFields. 我将$set在这里使用它,因为它映射了我们正在努力实现的目标。

db.collection.<update method>(
    {},
    [
        {"$set": {"name": { "$concat": ["$firstName", " ", "$lastName"]}}}
    ]
)

MongoDB 3.4+

MongoDB 3.4+

In 3.4+ you can use $addFieldsand the $outaggregation pipeline operators.

在3.4+,你可以使用$addFields$out聚集管道运营商。

db.collection.aggregate(
    [
        { "$addFields": { 
            "name": { "$concat": [ "$firstName", " ", "$lastName" ] } 
        }},
        { "$out": "collection" }
    ]
)

Note that this does not update your collection but instead replace the existing collection or create a new one.Also for update operations that require "type casting" you will need client side processing,and depending on the operation, you may need to use the find()method instead of the .aggreate()method.

请注意,这不会更新您的收藏,而是替换现有收藏或创建一个新收藏。同样对于需要“类型转换”的更新操作,您将需要客户端处理,并且根据操作,您可能需要使用find()方法而不是.aggreate()方法。

MongoDB 3.2 and 3.0

MongoDB 3.2 和 3.0

The way we do this is by $projecting our documents and use the $concatstring aggregation operator to return the concatenated string. we From there, you then iterate the cursorand use the $setupdate operator to add the new field to your documents using bulk operationsfor maximum efficiency.

我们这样做的方法是通过$projecting 我们的文档并使用$concat字符串聚合运算符来返回连接的字符串。we 从那里,您然后迭代游标并使用$set更新操作符使用批量操作将新字段添加到您的文档中以获得最大效率。

Aggregation query:

聚合查询:

var cursor = db.collection.aggregate([ 
    { "$project":  { 
        "name": { "$concat": [ "$firstName", " ", "$lastName" ] } 
    }}
])

MongoDB 3.2 or newer

MongoDB 3.2 或更新版本

from this, you need to use the bulkWritemethod.

由此,您需要使用该bulkWrite方法。

var requests = [];
cursor.forEach(document => { 
    requests.push( { 
        'updateOne': {
            'filter': { '_id': document._id },
            'update': { '$set': { 'name': document.name } }
        }
    });
    if (requests.length === 500) {
        //Execute per 500 operations and re-init
        db.collection.bulkWrite(requests);
        requests = [];
    }
});

if(requests.length > 0) {
     db.collection.bulkWrite(requests);
}

MongoDB 2.6 and 3.0

MongoDB 2.6 和 3.0

From this version you need to use the now deprecated BulkAPI and its associated methods.

从这个版本开始,您需要使用现已弃用的BulkAPI 及其相关方法

var bulk = db.collection.initializeUnorderedBulkOp();
var count = 0;

cursor.snapshot().forEach(function(document) { 
    bulk.find({ '_id': document._id }).updateOne( {
        '$set': { 'name': document.name }
    });
    count++;
    if(count%500 === 0) {
        // Excecute per 500 operations and re-init
        bulk.execute();
        bulk = db.collection.initializeUnorderedBulkOp();
    }
})

// clean up queues
if(count > 0) {
    bulk.execute();
}

MongoDB 2.4

MongoDB 2.4

cursor["result"].forEach(function(document) {
    db.collection.update(
        { "_id": document._id }, 
        { "$set": { "name": document.name } }
    );
})

回答by Carlos Barcelona

You should iterate through. For your specific case:

你应该迭代。对于您的具体情况:

db.person.find().snapshot().forEach(
    function (elem) {
        db.person.update(
            {
                _id: elem._id
            },
            {
                $set: {
                    name: elem.firstname + ' ' + elem.lastname
                }
            }
        );
    }
);

回答by Niels van der Rest

Apparently there is a way to do this efficiently since MongoDB 3.4, see styvane's answer.

显然,自 MongoDB 3.4 以来,有一种方法可以有效地做到这一点,请参阅styvane 的回答



Obsolete answer below

下面的过时答案

You cannot refer to the document itself in an update (yet). You'll need to iterate through the documents and update each document using a function. See this answerfor an example, or this onefor server-side eval().

您不能在更新中引用文档本身(还)。您需要遍历文档并使用函数更新每个文档。见这个答案的一个例子,或者这一个服务器端eval()

回答by Eric Kigathi

For a database with high activity, you may run into issues where your updates affect actively changing records and for this reason I recommend using snapshot()

对于活动量大的数据库,您可能会遇到更新影响主动更改记录的问题,因此我建议使用snapshot()

db.person.find().snapshot().forEach( function (hombre) {
    hombre.name = hombre.firstName + ' ' + hombre.lastName; 
    db.person.save(hombre); 
});

http://docs.mongodb.org/manual/reference/method/cursor.snapshot/

http://docs.mongodb.org/manual/reference/method/cursor.snapshot/

回答by Aldo

Regarding this answer, the snapshot function is deprecated in version 3.6, according to this update. So, on version 3.6 and above, it is possible to perform the operation this way:

关于此答案,根据此更新,快照功能在 3.6 版中已弃用。因此,在 3.6 及更高版本上,可以通过以下方式执行操作:

db.person.find().forEach(
    function (elem) {
        db.person.update(
            {
                _id: elem._id
            },
            {
                $set: {
                    name: elem.firstname + ' ' + elem.lastname
                }
            }
        );
    }
);

回答by Xavier Guihot

Starting Mongo 4.2, db.collection.update()can accept an aggregation pipeline, finally allowing the update/creation of a field based on another field:

开始Mongo 4.2db.collection.update()可以接受聚合管道,最后允许基于另一个字段更新/创建一个字段:

// { firstName: "Hello", lastName: "World" }
db.collection.update(
  {},
  [{ $set: { name: { $concat: [ "$firstName", " ", "$lastName" ] } } }],
  { multi: true }
)
// { "firstName" : "Hello", "lastName" : "World", "name" : "Hello World" }
  • The first part {}is the match query, filtering which documents to update (in our case all documents).

  • The second part [{ $set: { name: { ... } }]is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline). $setis a new aggregation operator and an alias of $addFields.

  • Don't forget { multi: true }, otherwise only the first matching document will be updated.

  • 第一部分{}是匹配查询,过滤要更新的文档(在我们的例子中是所有文档)。

  • 第二部分[{ $set: { name: { ... } }]是更新聚合管道(注意方括号表示使用聚合管道)。$set是一个新的聚合运算符,是 的别名$addFields

  • 不要忘记{ multi: true },否则只会更新第一个匹配的文档。

回答by Chris Gibb

I tried the above solution but I found it unsuitable for large amounts of data. I then discovered the stream feature:

我尝试了上述解决方案,但发现它不适用于大量数据。然后我发现了流功能:

MongoClient.connect("...", function(err, db){
    var c = db.collection('yourCollection');
    var s = c.find({/* your query */}).stream();
    s.on('data', function(doc){
        c.update({_id: doc._id}, {$set: {name : doc.firstName + ' ' + doc.lastName}}, function(err, result) { /* result == true? */} }
    });
    s.on('end', function(){
        // stream can end before all your updates do if you have a lot
    })
})

回答by Chris Bloom

Here's what we came up with for copying one field to another for ~150_000 records. It took about 6 minutes, but is still significantly less resource intensive than it would have been to instantiate and iterate over the same number of ruby objects.

以下是我们为将一个字段复制到另一个字段约 150_000 条记录的方法。大约需要 6 分钟,但与实例化和迭代相同数量的 ruby​​ 对象相比,资源密集程度仍然要低得多。

js_query = %({
  $or : [
    {
      'settings.mobile_notifications' : { $exists : false },
      'settings.mobile_admin_notifications' : { $exists : false }
    }
  ]
})

js_for_each = %(function(user) {
  if (!user.settings.hasOwnProperty('mobile_notifications')) {
    user.settings.mobile_notifications = user.settings.email_notifications;
  }
  if (!user.settings.hasOwnProperty('mobile_admin_notifications')) {
    user.settings.mobile_admin_notifications = user.settings.email_admin_notifications;
  }
  db.users.save(user);
})

js = "db.users.find(#{js_query}).forEach(#{js_for_each});"
Mongoid::Sessions.default.command('$eval' => js)

回答by Yi Xiang Chong

With MongoDB version 4.2+, updates are more flexible as it allows the use of aggregation pipeline in its update, updateOneand updateMany. You can now transform your documents using the aggregation operators then update without the need to explicity state the $setcommand (instead we use $replaceRoot: {newRoot: "$$ROOT"})

使用MongoDB 4.2+ 版,更新更加灵活,因为它允许在其update,updateOneupdateMany. 您现在可以使用聚合运算符转换您的文档,然后更新,而无需明确说明$set命令(我们使用$replaceRoot: {newRoot: "$$ROOT"}

Here we use the aggregate query to extract the timestamp from MongoDB's ObjectID "_id" field and update the documents (I am not an expert in SQL but I think SQL does not provide any auto generated ObjectID that has timestamp to it, you would have to automatically create that date)

在这里,我们使用聚合查询从 MongoDB 的 ObjectID "_id" 字段中提取时间戳并更新文档(我不是 SQL 专家,但我认为 SQL 不提供任何具有时间戳的自动生成的 ObjectID,您必须自动创建该日期)

var collection = "person"

agg_query = [
    {
        "$addFields" : {
            "_last_updated" : {
                "$toDate" : "$_id"
            }
        }
    },
    {
        $replaceRoot: {
            newRoot: "$$ROOT"
        } 
    }
]

db.getCollection(collection).updateMany({}, agg_query, {upsert: true})