किसी अन्य फ़ील्ड के मान का उपयोग करके MongoDB फ़ील्ड अपडेट करें


372

MongoDB में, क्या किसी अन्य फ़ील्ड से मान का उपयोग करके किसी फ़ील्ड का मान अपडेट करना संभव है? समतुल्य एसक्यूएल कुछ इस तरह होगा:

UPDATE Person SET Name = FirstName + ' ' + LastName

और MongoDB छद्म कोड होगा:

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

जवाबों:


258

ऐसा करने का सबसे अच्छा तरीका संस्करण 4.2+ है जो अपडेट दस्तावेज़ और updateOne, updateManyया updateसंग्रह विधि में एकत्रीकरण पाइपलाइन का उपयोग करने की अनुमति देता है । ध्यान दें कि बाद में सभी भाषाओं के चालकों को नहीं तो अधिकांश में पदावनत कर दिया गया है।

MongoDB 4.2+

संस्करण 4.2 ने $setपाइपलाइन चरण ऑपरेटर भी पेश किया, जो इसके लिए एक उपनाम है $addFields। मैं $setयहां उपयोग करूंगा क्योंकि यह उन मानचित्रों के साथ है जिन्हें हम हासिल करने की कोशिश कर रहे हैं।

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

MongoDB 3.4+

3.4+ में आप उपयोग कर सकते हैं $addFieldsऔर $outएकत्रीकरण पाइपलाइन ऑपरेटर।

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

ध्यान दें कि यह आपके संग्रह को अपडेट नहीं करता है, बल्कि मौजूदा संग्रह को प्रतिस्थापित करता है या एक नया बनाता है। इसके अलावा अद्यतन कार्यों के लिए जिन्हें "टाइप कास्टिंग" की आवश्यकता होती है, आपको क्लाइंट साइड प्रोसेसिंग की आवश्यकता होगी, और ऑपरेशन के आधार पर, आपको find()विधि के बजाय विधि का उपयोग करने की आवश्यकता हो सकती है .aggreate()

MongoDB 3.2 और 3.0

जिस तरह से हम ऐसा करते हैं वह $projectहमारे दस्तावेज़ों द्वारा किया जाता है और $concatसमवर्ती स्ट्रिंग को वापस करने के लिए स्ट्रिंग एकत्रीकरण ऑपरेटर का उपयोग करता है । हम वहां से, फिर आप कर्सर को पुनरावृत्त करते हैं और अधिकतम दक्षता के लिए बल्क ऑपरेशंस$set का उपयोग करके अपने दस्तावेज़ों में नया फ़ील्ड जोड़ने के लिए अपडेट ऑपरेटर का उपयोग करते हैं ।

एकत्रीकरण क्वेरी:

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

MongoDB 3.2 या नया

इस से, आपको 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 और 3.0

इस संस्करण से आपको अब अपग्रेड किए गए Bulkएपीआई और उससे जुड़े तरीकों का उपयोग करने की आवश्यकता है ।

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 या नए" के कोड के साथ एक समस्या है। चूँकि forEach async है इसलिए कुछ भी आमतौर पर अंतिम बल्क वर्इट में नहीं लिखा होगा।
विक्टर हेडफॉक

3
4.2+ काम नहीं करता है। MongoError: डॉलर ($) उपसर्गित क्षेत्र '$ concat' में 'name। $ Concat' भंडारण के लिए मान्य नहीं है।
जोश वुडकॉक

@JoshWoodcock, मुझे लगता है कि आपके द्वारा चलाए जा रहे प्रश्न में आपको टाइपो था। मेरा सुझाव है कि आप दोहरी जांच करें।
स्टाइलवेन

@JoshWoodcock यह खूबसूरती से काम करता है। कृपया MongoDB Web Shell
styvane

2
उसी समस्या में चलने वालों के लिए @JoshWoodcock ने वर्णन किया: ध्यान दें कि 4.2+ के लिए उत्तर एक एकत्रीकरण पाइपलाइन का वर्णन करता है , इसलिए दूसरे पैरामीटर में वर्ग कोष्ठक को याद न करें !
philsch

240

आपको इसके माध्यम से पुनरावृति करनी चाहिए। आपके विशिष्ट मामले के लिए:

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

4
यदि किसी अन्य उपयोगकर्ता ने आपके खोज () और आपके बचाने () के बीच दस्तावेज़ को बदल दिया है तो क्या होगा?
UpTheCreek

3
सच है, लेकिन खेतों के बीच नकल करने के लिए लेनदेन को परमाणु होने की आवश्यकता नहीं होनी चाहिए।
UpTheCreek

3
यह ध्यान रखना महत्वपूर्ण है कि save()दस्तावेज़ को पूरी तरह से बदल देता है। के update()बजाय का उपयोग करना चाहिए ।
कार्लोस

12
कैसे के बारे मेंdb.person.update( { _id: elem._id }, { $set: { name: elem.firstname + ' ' + elem.lastname } } );
फिलिप जर्दास

1
मैंने एक फ़ंक्शन बनाया, create_guidजो केवल दस्तावेज़ के प्रति एक अद्वितीय मार्गदर्शिका का उत्पादन करता है जब forEachइस तरह से पुनरावृत्ति होती है (अर्थात केवल create_guidएक updateबयान में mutli=trueसभी दस्तावेजों के लिए एक ही गाइड के उत्पन्न होने का कारण होता है)। इस जवाब ने मेरे लिए पूरी तरह से काम किया। +1
रमीराबेल

103

जाहिरा तौर पर MongoDB 3.4 के बाद से कुशलता से ऐसा करने का एक तरीका है, स्टाइलवैन का जवाब देखें


नीचे अप्रचलित उत्तर

आप दस्तावेज़ को अपडेट (अभी तक) में संदर्भित नहीं कर सकते। आपको दस्तावेज़ों के माध्यम से पुनरावृत्त करना होगा और फ़ंक्शन का उपयोग करके प्रत्येक दस्तावेज़ को अपडेट करना होगा। उदाहरण के लिए यह उत्तर देखें , या सर्वर-साइड के लिए यह एक हैeval()


31
क्या यह आज भी मान्य है?
क्रिश्चियन एंगेल

3
@ChristianEngel: ऐसा प्रतीत होता है। मैं MongoDB डॉक्स में कुछ भी नहीं पा रहा था जो एक updateऑपरेशन में वर्तमान दस्तावेज़ के संदर्भ का उल्लेख करता है । यह संबंधित सुविधा अनुरोध अभी भी अनसुलझा है।
नील्स वैन डर रेस्ट

4
क्या यह अप्रैल 2017 में अभी भी वैध है? या पहले से ही नई सुविधाएँ हैं जो ऐसा कर सकती हैं?
किम

1
@ किम ऐसा लग रहा है कि यह अभी भी वैध है। इसके अलावा, फ़ीचर अनुरोध है कि @ नील्स-वैन-डेर-रेस्ट 2013 में वापस इंगित किया गया था OPEN
डेंजिगर

8
यह एक वैध जवाब नहीं है, @styvane के जवाब पर एक नज़र है
aitchkhan

45

उच्च गतिविधि वाले डेटाबेस के लिए, आप उन मुद्दों पर चल सकते हैं जहां आपके अपडेट सक्रिय रूप से रिकॉर्ड बदलने को प्रभावित करते हैं और इस कारण से मैं स्नैपशॉट का उपयोग करने की सलाह देता हूं ()

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
यदि किसी अन्य उपयोगकर्ता ने खोज () और सहेजें () के बीच के व्यक्ति को संपादित किया तो क्या होगा? मेरे पास एक मामला है जहां एक ही वस्तु पर कई कॉल किए जा सकते हैं जो उन्हें उनके वर्तमान मूल्यों के आधार पर बदल सकते हैं। 2 उपयोगकर्ता को तब तक पढ़ने के साथ इंतजार करना चाहिए जब तक कि बचत के साथ 1 नहीं किया जाता है। क्या यह पूरा करता है?
मार्को

4
के बारे में 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. लिंक
ppython 14

10

इस उत्तर के संबंध में , स्नैपशॉट फ़ंक्शन को इस अद्यतन के अनुसार, संस्करण 3.6 में चित्रित किया गया है । तो, संस्करण 3.6 और इसके बाद के संस्करण पर, इस तरह से ऑपरेशन करना संभव है:

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

9

शुरू करना 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" }
  • पहला भाग {}मैच क्वेरी है, जिसे छानने के लिए दस्तावेजों को अपडेट करना है (हमारे मामले में सभी दस्तावेज)।

  • दूसरा भाग [{ $set: { name: { ... } }]अद्यतन एकत्रीकरण पाइपलाइन है (ध्यान दें कि चौकोर कोष्ठक एक एकत्रीकरण पाइपलाइन के उपयोग को दर्शाता है)। $setएक नया एकत्रीकरण ऑपरेटर और का एक अन्य नाम है $addFields

  • मत भूलना { multi: true }, अन्यथा केवल पहले मिलान दस्तावेज़ को अपडेट किया जाएगा।


8

मैंने उपरोक्त समाधान की कोशिश की, लेकिन मैंने इसे बड़ी मात्रा में डेटा के लिए अनुपयुक्त पाया। मैंने तब स्ट्रीम फीचर की खोज की:

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
यह कैसे अलग है? क्या अद्यतन गतिविधि द्वारा भाप का गला घोंटा जाएगा? क्या आपके पास इसका कोई संदर्भ है? मानगो डॉक्स काफी खराब हैं।
निको

2

यहाँ हम ~ 150_000 रिकॉर्ड के लिए एक क्षेत्र से दूसरे क्षेत्र की प्रतिलिपि बनाने के लिए आए हैं। इसमें लगभग 6 मिनट का समय लगा, लेकिन फिर भी यह काफी कम संसाधन गहन है कि यह एक ही संख्या में माणिक वस्तुओं पर त्वरित और पुनरावृति होता।

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

साथ MongoDB संस्करण 4.2+ , अद्यतन और अधिक लचीला के रूप में यह अपने में एकत्रीकरण पाइप लाइन के उपयोग की अनुमति देता हैं update, updateOneऔर updateMany। अब आप एकत्रीकरण संचालकों का उपयोग करके अपने दस्तावेजों को बदल सकते हैं, फिर अन्वेषण की आवश्यकता के बिना $setकमांड को अपडेट कर सकते हैं (इसके बजाय हम उपयोग करते हैं$replaceRoot: {newRoot: "$$ROOT"} )

यहां हम MongoDB के ऑब्जेक्ट "_id" फ़ील्ड से टाइमस्टैम्प को निकालने के लिए कुल क्वेरी का उपयोग करते हैं और दस्तावेज़ों को अपडेट करते हैं (मैं SQL में विशेषज्ञ नहीं हूं, लेकिन मुझे लगता है कि SQL किसी भी ऑटो उत्पन्न ऑब्जेक्ट को प्रदान नहीं करता है जो उसके लिए टाइमस्टैम्प है, आपको करना होगा स्वचालित रूप से वह तिथि बनाएं)

var collection = "person"

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

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

आप की जरूरत नहीं है { $replaceRoot: { newRoot: "$$ROOT" } }; इसका मतलब है कि दस्तावेज़ को स्वयं से बदलना, जो व्यर्थ है। यदि आप $addFieldsइसके उपनाम से प्रतिस्थापित करते हैं $setऔर updateManyजो इसके लिए उपनामों में से एक है update, तो आपको ठीक उसी उत्तर के रूप में मिलता है जैसे कि यह ऊपर।
ज़ेवियर गुहोट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.