В MongoDB можно ли обновить значение поля, используя значение из другого поля? Эквивалентный SQL будет примерно таким:
UPDATE Person SET Name = FirstName + ' ' + LastName
И псевдокод MongoDB будет:
db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );
Видимо, есть способ сделать это эффективно, начиная с MongoDB 3.4, см. Ответ стиване.
Устаревший ответ ниже
Вы не можете ссылаться на сам документ в обновлении (пока). Вам нужно будет перебирать документы и обновлять каждый документ с помощью функции. Посмотрите этот ответ для примера, или этот для серверной eval()
.
update
. Этот связанный запрос по-прежнему не решен.
Вы должны перебирать. Для вашего конкретного случая:
db.person.find().snapshot().forEach(
function (elem) {
db.person.update(
{
_id: elem._id
},
{
$set: {
name: elem.firstname + ' ' + elem.lastname
}
}
);
}
);
Лучший способ сделать это - использовать структуру агрегации для вычисления нашего нового поля.
Наиболее эффективное решение в MongoDB 3.4 с помощью $addFields
и $out
операторов компоновки агрегации.
db.collection.aggregate(
[
{ "$addFields": {
"name": { "$concat": [ "$firstName", " ", "$lastName" ] }
}},
{ "$out": "collection" }
]
)
Обратите внимание, что этот не обновляет вашу коллекцию, а вместо этого заменяет существующую коллекцию или создает новую. Также для операций обновления, требующих "типа casting" вам потребуется обработка на стороне клиента, и в зависимости от операции вам может понадобиться использовать метод find()
вместо метода .aggreate()
.
Как мы это делаем, $project
в наших документах и используйте $concat
, чтобы вернуть конкатенированную строку.
we Оттуда вы затем перебираете курсор и используете оператор обновления $set
, чтобы добавить новое поле в свои документы, используя массовые операции для максимальной эффективности.
var cursor = db.collection.aggregate([
{ "$project": {
"name": { "$concat": [ "$firstName", " ", "$lastName" ] }
}}
])
вам нужно использовать метод 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);
}
В этой версии вам нужно использовать теперь устаревший 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();
}
cursor["result"].forEach(function(document) {
db.collection.update(
{ "_id": document._id },
{ "$set": { "name": document.name } }
);
})
.length
каждая итерация в монго так же медленно, как обычный javascript, где он пересчитывает длину при каждом вызове?
length
проверяется на каждой итерации. Это то, что мне нужно будет проверить позже. Другой вариант, если это медленно, это использовать счетчик, который вы затем увеличиваете на 1 на каждой итерации.
Для базы данных с высокой активностью вы можете столкнуться с проблемами, когда ваши обновления влияют на активное изменение записей, и по этой причине я рекомендую использовать снимок()
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/
snapshot()
: Deprecated in the mongo Shell since v3.2. Starting in v3.2, the $snapshot operator is deprecated in the mongo shell. In the mongo shell, use cursor.snapshot() instead.
ссылка на сайт
Я попробовал вышеуказанное решение, но я счел его непригодным для больших объемов данных. Затем я обнаружил функцию потока:
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
})
})
Вот что мы придумали для копирования одного поля в другое для ~ 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)
Что касается этого ответа, функция моментального снимка устарела в версии 3.6, согласно этому обновлению. Итак, на версии 3.6 и выше, можно выполнить операцию следующим образом:
db.person.find().forEach(
function (elem) {
db.person.update(
{
_id: elem._id
},
{
$set: {
name: elem.firstname + ' ' + elem.lastname
}
}
);
}
);