sql >> Databasteknik >  >> NoSQL >> MongoDB

Lägg upp dokument och/eller lägg till ett underdokument

Tillvägagångssättet för att hantera detta är inte enkelt, eftersom att blanda "upserts" med att lägga till objekt i "arrays" lätt kan leda till oönskade resultat. Det beror också på om du vill ha logik för att ställa in andra fält såsom en "räknare" som anger hur många kontakter det finns inom en array, som du bara vill öka/minska när objekt läggs till respektive tas bort.

Men i det enklaste fallet, om "kontakterna" bara innehöll ett singularvärde som ett ObjectId länkar till en annan samling, sedan $addToSet modifieraren fungerar bra, så länge det inte finns några "räknare" inblandade:

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

Och det är helt okej eftersom du bara testar för att se om ett dokument matchar "klientnamnet", om inte upphäv det. Oavsett om det finns en matchning eller inte, $addToSet operatören kommer att ta hand om unika "singulära" värden, vilket är alla "objekt" som verkligen är unika.

Svårigheterna kommer in där du har något som:

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

Redan i kontaktmatrisen, och då vill du göra något så här:

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

Där din egentliga avsikt är att detta är "samma" John Smith, och det är bara att "åldern" inte är annorlunda. Helst vill du bara "uppdatera" den arrayposten och inte skapa en ny array eller ett nytt dokument.

Arbetar med detta med .findOneAndUpdate() var du vill att det uppdaterade dokumentet ska returneras kan vara svårt. Så om du inte verkligen vill ha det modifierade dokumentet som svar, då Bulk Operations API av MongoDB och kärndrivrutinen är till stor hjälp här.

Med tanke på uttalandena:

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

Detta är trevligt eftersom Bulk Operations här innebär att alla uttalanden här skickas till servern på en gång och det finns bara ett svar. Notera också här att logiken här betyder att högst två operationer faktiskt kommer att modifiera någonting.

I första hand $setOnInsert modifier ser till att ingenting ändras när dokumentet bara är en matchning. Eftersom de enda ändringarna här är inom det blocket, påverkar detta bara ett dokument där en "upsert" inträffar.

Notera även på de två följande påståendena att du inte försöker "upphäva" igen. Detta anser att det första uttalandet möjligen var framgångsrikt där det behövde vara, eller på annat sätt inte spelade någon roll.

Den andra anledningen till att det inte finns någon "upsert" där är att de villkor som behövs för att testa närvaron av elementet i arrayen skulle leda till "upsert" av ett nytt dokument när de inte uppfylldes. Det är inte önskvärt, därför ingen "upsert".

Vad de faktiskt gör är att kontrollera om arrayelementet finns eller inte, och antingen uppdatera det befintliga elementet eller skapa ett nytt. Därför totalt sett innebär alla operationer att du antingen ändrar "en gång" eller högst "två gånger" i det fall en uppsving inträffade. Det möjliga "två gånger" skapar väldigt lite omkostnader och inga egentliga problem.

Även i det tredje uttalandet $not operatorn omvänder logiken för $elemMatch för att fastställa att det inte finns något arrayelement med frågevillkoret.

Översätter detta med .findOneAndUpdate() blir lite mer ett problem. Det är inte bara "framgången" som är viktig nu, den avgör också hur det slutliga innehållet returneras.

Så den bästa idén här är att köra händelserna i "serier" och sedan arbeta lite magiskt med resultatet för att returnera det "uppdaterade" slutformuläret.

Hjälpen vi kommer att använda här är både med async.waterfall och lodash bibliotek:

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

Det följer samma logik som tidigare i att bara två eller ett av dessa uttalanden faktiskt kommer att göra något med möjligheten att det "nya" dokumentet som returneras kommer att vara null . "Vattenfallet" skickar här ett resultat från varje steg till nästa, inklusive slutet där även eventuella fel omedelbart förgrenas till.

I det här fallet null skulle bytas ut mot ett tomt objekt {} och _.merge() metod kommer att kombinera de två objekten till ett, i varje senare skede. Detta ger dig det slutliga resultatet som är det modifierade objektet, oavsett vilka föregående operationer som faktiskt gjorde någonting.

Naturligtvis skulle det krävas en annan manipulation för $pull , och även din fråga har indata som en objektform i sig. Men det är faktiskt svar i sig.

Detta bör åtminstone få dig igång med hur du ska närma dig ditt uppdateringsmönster.



  1. Distribuera en nodejs-app till Googles molnplattform

  2. MongoDB $in Query Operator

  3. mongodb:hitta det högsta numeriska värdet i en kolumn

  4. Samtidighet i gopkg.in/mgo.v2 (Mongo, Go)