MongoDB alanını başka bir alanın değerini kullanarak güncelleme


372

MongoDB'de, başka bir alandaki değeri kullanarak bir alanın değerini güncellemek mümkün müdür? Eşdeğer SQL şuna benzer:

UPDATE Person SET Name = FirstName + ' ' + LastName

Ve MongoDB sözde kodu:

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

Yanıtlar:


259

Bunu yapmanın en iyi yolu, güncelleme belgesinde ve updateOne, updateManyveya updatetoplama yönteminde toplama boru hattının kullanılmasına izin veren 4.2 ve sonraki sürümlerdir . İkincisinin, tüm dil sürücüleri olmasa da çoğunda kullanımdan kaldırıldığını unutmayın.

MongoDB 4.2+

Sürüm 4.2 ayrıca $setbir takma ad olan boru hattı sahne operatörü tanıttı $addFields. Ben kullanacağı $seto kadar burada eşler biz başarmak için çalışıyoruz ne.

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

MongoDB 3.4+

3.4+ sürümünde $addFieldsve $outtoplama boru hattı operatörlerini kullanabilirsiniz.

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

Bu unutmayın koleksiyonunuzu güncellemek yerine varolan koleksiyon değiştirmek veya yeni bir tane oluşturmaz. Ayrıca "tür döküm" gerektiren güncelleme işlemleri için istemci tarafı işlemeye ihtiyacınız olacak ve işleme bağlı olarak, find()yöntem yerine yöntemi kullanmanız gerekebilir .aggreate().

MongoDB 3.2 ve 3.0

Bunu yapma şeklimiz $project, belgelerimizi oluşturarak ve $concatbirleştirilmiş dizeyi döndürmek için dize toplama operatörünü kullanmaktır . Oradan, daha sonra yineleme imleci ve kullanımı $setkullanarak belgelere yeni alan eklemek için güncelleme operatörü toplu işlemler maksimum verim için.

Toplama sorgusu:

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

MongoDB 3.2 veya daha yenisi

bundan, bulkWriteyöntemi kullanmanız gerekir .

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 ve 3.0

Bu sürümden, artık kullanımdan kaldırılmış BulkAPI'yı ve onunla ilişkili yöntemleri kullanmanız gerekir .

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

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

"MongoDB 3.2 veya daha yeni" koduyla ilgili bir sorun olduğunu düşünüyorum. ForEach zaman uyumsuz olduğundan hiçbir şey genellikle son bulkWrite içinde yazılmaz.
Viktor Hedefalk

3
4.2+ Çalışmıyor. MongoError: 'name. $ Concat' içindeki dolar ($) önek alanına '$ concat' alanı depolama için geçerli değil.
Josh Woodcock

@JoshWoodcock, çalıştırdığınız sorguda bir yazım hatası olduğunu düşünüyorum. Tekrar kontrol etmenizi öneririm.
styvane

@JoshWoodcock Güzel çalışıyor. Lütfen bunu MongoDB Web Shell
styvane

2
@JoshWoodcock aynı problemle karşılaşanlar için: 4.2+ için cevabın bir toplama boru hattını tanımladığına dikkat edin , bu yüzden ikinci parametrede köşeli parantezleri kaçırmayın !
philsch

240

Tekrar etmelisin. Özel durumunuz için:

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

4
Başka bir kullanıcı belgeyi find () ve save () öğeleriniz arasında değiştirirse ne olur?
UpTheCreek

3
Doğru, ancak alanlar arasında kopyalama işlemlerin atomik olmasını gerektirmemelidir.
UpTheCreek

3
save()Belgenin tamamen yerini aldığını fark etmek önemlidir . Bunun update()yerine kullanmalısınız .
Carlos

12
Peki yadb.person.update( { _id: elem._id }, { $set: { name: elem.firstname + ' ' + elem.lastname } } );
Philipp Jardas

1
Bu şekilde create_guidyineleme yaparken yalnızca belge başına benzersiz bir kılavuz üreten denilen bir işlev oluşturdum forEach(yani , tüm belgeler için aynı kılavuzun oluşturulmasına neden olan create_guidbir updatedeyim kullanarak mutli=true). Bu cevap benim için mükemmel çalıştı. +1
rmirabelle

103

Görünüşe göre bunu MongoDB 3.4'ten beri etkili bir şekilde yapmanın bir yolu var, bkz. Styvane cevabı .


Aşağıdaki eski cevap

Güncellemede (henüz) belgenin kendisine başvuramazsınız. Belgeler arasında yineleme yapmanız ve her belgeyi bir işlev kullanarak güncellemeniz gerekir. Bkz bu cevabı bir örnek için, ya bu bir sunucu tarafı için eval().


31
Bu bugün hala geçerli mi?
Christian Engel

3
@ChristianEngel: Öyle görünüyor. MongoDB belgelerinde, bir işlemdeki geçerli belgeye başvuruda bulunan hiçbir şey bulamadım update. Bu ilgili özellik isteği de hala çözülmedi.
Niels van der Rest

4
Nisan 2017'de hala geçerli mi? Yoksa bunu yapabilen yeni özellikler var mı?
Kim

1
@Kim Görünüşe göre hala geçerli. Ayrıca, @ niels-van-der-rest'in 2013'te belirttiği özellik talebi hala devam ediyor OPEN.
Danziger

8
bu artık geçerli bir cevap değil, @styvane yanıtına bir göz atın
aitchkhan

45

Yüksek aktiviteye sahip bir veritabanı için, güncellemelerinizin aktif olarak değişen kayıtları etkilediği sorunlarla karşılaşabilirsiniz ve bu nedenle anlık görüntü () kullanmanızı öneririm

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/


2
Başka bir kullanıcı kişiyi find () ve save () arasında düzenlediyse ne olur? Geçerli nesnelerine göre onları değiştirerek aynı nesneye birden çok çağrı yapılabilir bir durum var. 2. kullanıcının 1. kaydetme işlemi tamamlanıncaya kadar okumayı beklemesi gerekir. Bu bunu başarıyor mu?
Marco

4
Hakkında 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. link
ppython

10

Bu yanıtla ilgili olarak , bu güncelleştirmeye göre anlık görüntü işlevi 3.6 sürümünde kullanımdan kaldırılmıştır . Böylece, 3.6 ve üzeri sürümlerde, işlemi bu şekilde gerçekleştirmek mümkündür:

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

9

Başlangıçta Mongo 4.2, db.collection.update()bir toplama hattını kabul edebilir ve son olarak başka bir alana dayanan bir alanın güncellenmesine / oluşturulmasına izin verebilir:

// { firstName: "Hello", lastName: "World" }
db.collection.update(
  {},
  [{ $set: { name: { $concat: [ "$firstName", " ", "$lastName" ] } } }],
  { multi: true }
)
// { "firstName" : "Hello", "lastName" : "World", "name" : "Hello World" }
  • İlk bölüm {}hangi belgelerin güncelleneceğini filtreleyen eşleşme sorgusudur (bizim durumumuzda tüm belgeler).

  • İkinci bölüm [{ $set: { name: { ... } }]güncelleme toplama boru hattıdır (bir toplama boru hattının kullanımını belirten köşeli parantezlere dikkat edin). $setyeni bir toplama operatörü ve $addFields.

  • Unutmayın { multi: true }, aksi takdirde yalnızca eşleşen ilk belge güncellenir.


8

Yukarıdaki çözümü denedim ancak büyük miktarda veri için uygun bulmadım. Daha sonra akış özelliğini keşfettim:

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

1
Bu nasıl farklı? Güncelleme faaliyeti tarafından buhar kısıtlanacak mı? Herhangi bir referansınız var mı? Moğol dokümanlar oldukça zayıf.
Nico

2

İşte bir alanı başka bir alana kopyalamak için ~ 150_000 kayıt. Yaklaşık 6 dakika sürdü, ancak yine de aynı sayıda yakut nesne üzerinde yineleme ve yineleme yapmaktan çok daha az kaynak yoğun.

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)

1

İle MongoDB sürümü 4.2+ , güncellemeler o onun içinde toplanma boru hattının kullanımına izin olarak daha esnektir update, updateOneve updateMany. Artık, toplama işleçlerini kullanarak belgelerinizi dönüştürebilir, ardından $setkomutu açıklamaya gerek kalmadan güncelleyebilirsiniz (bunun yerine kullanırız $replaceRoot: {newRoot: "$$ROOT"})

Burada MongoDB ObjectID "_id" alanından zaman damgası ayıklamak ve belgeleri güncelleştirmek için toplu sorgu kullanın (SQL uzman değilim ama SQL ona zaman damgası olan herhangi bir otomatik oluşturulan ObjectID sağlamaz düşünüyorum, yapmanız gerekir bu tarihi otomatik olarak oluştur)

var collection = "person"

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

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

İhtiyacınız yok { $replaceRoot: { newRoot: "$$ROOT" } }; anlamsız olan belgeyi tek başına değiştirmek anlamına gelir. Eğer değiştirirseniz $addFieldsonun takma adı ile $setve updateManydiğer adlar biridir update, o zaman tam olarak aynı cevabı almak bu bir üzerindedir.
Xavier Guihot
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.