使用另一个字段的值更新 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
Update MongoDB field using value of another field
提问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
, updateMany
or update
collection method. Note that the latter has been deprecated in most if not all languages drivers.
最好的方法是在 4.2+ 版本中,它允许在更新文档和updateOne
,updateMany
或update
集合方法中使用聚合管道。请注意,后者在大多数语言驱动程序中已被弃用。
MongoDB 4.2+
MongoDB 4.2+
Version 4.2 also introduced the $set
pipeline stage operator which is an alias for $addFields
. I will use $set
here 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 $addFields
and the $out
aggregation 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 $project
ing our documents and use the $concat
string aggregation operator to return the concatenated string.
we From there, you then iterate the cursorand use the $set
update operator to add the new field to your documents using bulk operationsfor maximum efficiency.
我们这样做的方法是通过$project
ing 我们的文档并使用$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 bulkWrite
method.
由此,您需要使用该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 Bulk
API and its associated methods.
从这个版本开始,您需要使用现已弃用的Bulk
API 及其相关方法。
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.2
,db.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).$set
is 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
, updateOne
and updateMany
. You can now transform your documents using the aggregation operators then update without the need to explicity state the $set
command (instead we use $replaceRoot: {newRoot: "$$ROOT"}
)
使用MongoDB 4.2+ 版,更新更加灵活,因为它允许在其update
,updateOne
和updateMany
. 您现在可以使用聚合运算符转换您的文档,然后更新,而无需明确说明$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})