MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Добавяне на документ и/или добавяне на поддокумент

Подходът за справяне с това не е лесен, тъй като смесването на "upserts" с добавяне на елементи към "arrays" може лесно да доведе до нежелани резултати. Зависи също дали искате логика да зададе други полета, като например "брояч", показващ колко контакта има в рамките на масив, който искате да увеличавате/намалявате само когато елементите се добавят или премахват съответно.

В най-простия случай обаче, ако „контактите“ съдържат само единична стойност като ObjectId свързване към друга колекция, след което $addToSet модификаторът работи добре, стига да няма включени "броячи":

Client.findOneAndUpdate(
    { "clientName": clientName },
    { "$addToSet": { "contacts":  contact } },
    { "upsert": true, "new": true },
    function(err,client) {
        // handle here
    }
);

И всичко това е наред, тъй като вие само тествате, за да видите дали даден документ съвпада с "clientName", ако не го поставите нагоре. Независимо дали има съвпадение или не, $addToSet ще се погрижи за уникалните „единични“ стойности, като всеки „обект“, който е наистина уникален.

Трудностите идват там, където имате нещо като:

{ "firstName": "John", "lastName": "Smith", "age": 37 }

Вече сте в масива с контакти и искате да направите нещо подобно:

{ "firstName": "John", "lastName": "Smith", "age": 38 }

Където действителното ви намерение е, че това е „същият“ Джон Смит и просто „възрастта“ не е различна. В идеалния случай искате просто да „актуализирате“ този запис в масива, без да създавате нов масив или нов документ.

Работейки това с .findOneAndUpdate() къде искате да се върне актуализираният документ, може да е трудно. Така че, ако наистина не искате модифицираният документ в отговор, тогава API за групови операции на MongoDB и основния драйвер са от най-голяма помощ тук.

Имайки предвид твърденията:

var bulk = Client.collection.initializeOrderedBulkOP();

// First try the upsert and set the array
bulk.find({ "clientName": clientName }).upsert().updateOne({
    "$setOnInsert": { 
        // other valid client info in here
        "contacts": [contact]
    }
});

// Try to set the array where it exists
bulk.find({
    "clientName": clientName,
    "contacts": {
        "$elemMatch": {
            "firstName": contact.firstName,
            "lastName": contact.lastName
         }
    }
}).updateOne({
    "$set": { "contacts.$": contact }
});

// Try to "push" the array where it does not exist
bulk.find({
    "clientName": clientName,
    "contacts": {
        "$not": { "$elemMatch": {
            "firstName": contact.firstName,
            "lastName": contact.lastName
         }}
    }
}).updateOne({
    "$push": { "contacts": contact }
});

bulk.execute(function(err,response) {
    // handle in here
});

Това е хубаво, тъй като груповите операции тук означават, че всички изрази тук се изпращат на сървъра наведнъж и има само един отговор. Също така имайте предвид, че тук логиката означава, че най-много само две операции действително ще променят нещо.

В първия случай $setOnInsert модификатор гарантира, че нищо не се променя, когато документът е просто съвпадение. Тъй като единствените модификации тук са в рамките на този блок, това засяга само документ, където се появява „upsert“.

Също така обърнете внимание на следващите две изявления, които не се опитвайте да „обърнете“ отново. Това счита, че първият израз вероятно е бил успешен там, където е трябвало да бъде, или иначе няма значение.

Другата причина за липсата на "upsert" е, защото условията, необходими за тестване на присъствието на елемента в масива, биха довели до "upsert" на нов документ, когато не са изпълнени. Това не е желателно, следователно няма "upsert".

Това, което всъщност правят, е съответно да проверят дали елементът от масива присъства или не и или да актуализират съществуващия елемент, или да създадат нов. Следователно общо всички операции означават, че или модифицирате „веднъж“ или най-много „два пъти“ в случай, че е възникнала промяна. Възможното „два пъти“ създава много малко режийни разходи и никакъв реален проблем.

Също така в третото изявление $not операторът обръща логиката на $elemMatch за да определите, че не съществува елемент от масив с условието на заявката.

Превеждайки това с .findOneAndUpdate() става малко по-голям проблем. Не само „успехът“ е важен сега, той също определя как се връща евентуалното съдържание.

Така че най-добрата идея тук е да стартирате събитията в "серии" и след това да поработите малко с резултата, за да върнете крайния "актуализиран" формуляр.

Помощта, която ще използваме тук, е както с async.waterfall и lodash библиотека:

var _ = require('lodash');   // letting you know where _ is coming from

async.waterfall(
    [
        function(callback) {
            Client.findOneAndUpdate(
               { "clientName": clientName },
               {
                  "$setOnInsert": { 
                      // other valid client info in here
                      "contacts": [contact]
                  }
               },
               { "upsert": true, "new": true },
               callback
            );
        },
        function(client,callback) {
            Client.findOneAndUpdate(
                {
                    "clientName": clientName,
                    "contacts": {
                       "$elemMatch": {
                           "firstName": contact.firstName,
                           "lastName": contact.lastName
                       }
                    }
                },
                { "$set": { "contacts.$": contact } },
                { "new": true },
                function(err,newClient) {
                    client = client || {};
                    newClient = newClient || {};
                    client = _.merge(client,newClient);
                    callback(err,client);
                }
            );
        },
        function(client,callback) {
            Client.findOneAndUpdate(
                {
                    "clientName": clientName,
                    "contacts": {
                       "$not": { "$elemMatch": {
                           "firstName": contact.firstName,
                           "lastName": contact.lastName
                       }}
                    }
                },
                { "$push": { "contacts": contact } },
                { "new": true },
                function(err,newClient) {
                    newClient = newClient || {};
                    client = _.merge(client,newClient);
                    callback(err,client);
                }
            );
        }
    ],
    function(err,client) {
        if (err) throw err;
        console.log(client);
    }
);

Това следва същата логика, както преди, че само два или един от тези изрази всъщност ще направят нещо с възможността върнатият „нов“ документ да бъде null . „Водопадът“ тук предава резултат от всеки етап към следващия, включително края, към който незабавно ще се разклони всяка грешка.

В този случай null ще бъдат заменени с празен обект {} и _.merge() методът ще комбинира двата обекта в един на всеки следващ етап. Това ви дава крайния резултат, който е модифицираният обект, без значение кои предходни операции всъщност са направили нещо.

Разбира се, ще има различна манипулация, необходима за $pull , а също така вашият въпрос има входни данни като форма на обект сам по себе си. Но това всъщност са отговори сами по себе си.

Това поне трябва да ви помогне да разберете как да подходите към модела си за актуализиране.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Частична актуализация на поддокумент с nodejs/mongoose

  2. Как да актуализирам вграден документ в mongoose?

  3. Получаване на ObjectIdHex стойност от mgo заявка

  4. Вземете общ брой в Sails JS blueprint API

  5. Как да търсите документ по oid в mongoengine