sql >> Database >  >> NoSQL >> MongoDB

Document opwaarderen en/of een subdocument toevoegen

De aanpak om dit aan te pakken is niet eenvoudig, omdat het mengen van "upserts" met het toevoegen van items aan "arrays" gemakkelijk tot ongewenste resultaten kan leiden. Het hangt er ook van af of je logica wilt om andere velden in te stellen, zoals een "teller" die aangeeft hoeveel contacten er in een array zijn, die je alleen wilt verhogen/verlagen als items respectievelijk worden toegevoegd of verwijderd.

In het meest eenvoudige geval echter, als de "contacten" slechts een enkelvoudige waarde bevatten, zoals een ObjectId linken naar een andere collectie, dan de $addToSet modifier werkt goed, zolang er geen "tellers" bij betrokken zijn:

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

En dat is allemaal prima, want je test alleen om te zien of een document overeenkomt met de "clientName", zo niet upsert. Of er nu een overeenkomst is of niet, de $addToSet operator zorgt voor unieke "singuliere" waarden, zijnde elk "object" dat echt uniek is.

De moeilijkheden komen binnen als je iets hebt als:

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

Al in de contactenreeks, en dan wil je zoiets als dit doen:

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

Waar uw werkelijke bedoeling is dat dit de "dezelfde" John Smith is, en het is gewoon dat de "leeftijd" niet anders is. In het ideale geval wilt u dat array-item gewoon "bijwerken" en niet een nieuwe array of een nieuw document maken.

Dit werkend met .findOneAndUpdate() waar u het bijgewerkte document wilt laten terugkeren, kan moeilijk zijn. Dus als u het gewijzigde document niet echt wilt als reactie, dan is de API voor bulkbewerkingen van MongoDB en de kerndriver zijn hier het meest nuttig.

Gezien de uitspraken:

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

Dit is prettig, aangezien de bulkbewerkingen hier betekenen dat alle instructies hier in één keer naar de server worden verzonden en er slechts één antwoord is. Merk hier ook op dat de logica hier betekent dat hoogstens slechts twee bewerkingen daadwerkelijk iets zullen wijzigen.

In eerste instantie de $setOnInsert modifier zorgt ervoor dat er niets wordt gewijzigd wanneer het document slechts een overeenkomst is. Aangezien de enige wijzigingen hier binnen dat blok vallen, is dit alleen van invloed op een document waar een "upsert" voorkomt.

Merk ook op dat u bij de volgende twee uitspraken niet opnieuw probeert te "upsteren". Dit is van mening dat de eerste verklaring mogelijk succesvol was waar hij moest zijn, of anders niet uitmaakte.

De andere reden waarom er geen "upsert" is, is dat de voorwaarden die nodig zijn om de aanwezigheid van het element in de array te testen, zouden leiden tot de "upsert" van een nieuw document wanneer niet aan deze voorwaarden werd voldaan. Dat is niet gewenst, dus geen "upsert".

Wat ze in feite doen, is respectievelijk controleren of het array-element aanwezig is of niet, en ofwel het bestaande element bijwerken of een nieuw maken. Daarom betekenen alle bewerkingen in totaal dat u ofwel "een keer" of maximaal "twee keer" wijzigt in het geval dat er een upsert is opgetreden. De mogelijke "tweemaal" zorgt voor heel weinig overhead en geen echt probleem.

Ook in de derde verklaring de $not operator keert de logica om van de $elemMatch om te bepalen dat er geen array-element met de queryvoorwaarde bestaat.

Dit vertalen met .findOneAndUpdate() wordt wat meer een issue. Het is niet alleen het "succes" dat er nu toe doet, het bepaalt ook hoe de uiteindelijke inhoud wordt geretourneerd.

Dus het beste idee hier is om de gebeurtenissen in "serie" uit te voeren en dan een beetje magie te gebruiken met het resultaat om het einde "bijgewerkt" formulier terug te sturen.

De hulp die we hier zullen gebruiken is beide met async.waterfall en de lodash bibliotheek:

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

Dat volgt dezelfde logica als voorheen, in die zin dat slechts twee of één van die verklaringen daadwerkelijk iets zullen doen met de mogelijkheid dat het geretourneerde "nieuwe" document null zal zijn . De "waterval" geeft hier een resultaat van elke fase door aan de volgende, inclusief het einde waar ook een fout onmiddellijk naar toe zal gaan.

In dit geval is de null zou worden verwisseld voor een leeg object {} en de _.merge() methode zal de twee objecten in elk later stadium tot één combineren. Dit geeft u het uiteindelijke resultaat dat het gewijzigde object is, ongeacht welke voorgaande bewerkingen daadwerkelijk iets hebben gedaan.

Natuurlijk zou er een andere manipulatie nodig zijn voor $pull , en ook uw vraag heeft invoergegevens als een objectvorm op zich. Maar dat zijn eigenlijk antwoorden op zich.

Dit zou u in ieder geval op weg moeten helpen met het benaderen van uw updatepatroon.



  1. Hoe N-nummers van documenten in Mongodb te verwijderen

  2. Redis Verbinding via socket op Node.js

  3. Mongo filterarray van array van array

  4. Hoe een BsonDocument-object terug naar klasse te deserialiseren?