MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Dokument einfügen und/oder Unterdokument hinzufügen

Der Ansatz, dies zu handhaben, ist nicht einfach, da das Mischen von "Upserts" mit dem Hinzufügen von Elementen zu "Arrays" leicht zu unerwünschten Ergebnissen führen kann. Es hängt auch davon ab, ob Sie möchten, dass die Logik andere Felder wie einen "Zähler" setzt, der angibt, wie viele Kontakte sich in einem Array befinden, die Sie nur erhöhen/verringern möchten, wenn Elemente hinzugefügt oder entfernt werden.

Im einfachsten Fall jedoch, wenn die "Kontakte" nur einen singulären Wert wie zB eine ObjectId enthielten Verlinkung zu einer anderen Sammlung, dann $addToSet Modifikator funktioniert gut, solange keine "Zähler" beteiligt sind:

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

Und das ist alles in Ordnung, da Sie nur testen, ob ein Dokument mit dem "clientName" übereinstimmt, wenn nicht, fügen Sie es ein. Ob es eine Übereinstimmung gibt oder nicht, der $addToSet Der Operator kümmert sich um eindeutige "singuläre" Werte, die jedes "Objekt" sind, das wirklich einzigartig ist.

Die Schwierigkeiten treten auf, wenn Sie etwas haben wie:

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

Bereits im Kontakte-Array, und dann möchten Sie so etwas tun:

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

Wobei Ihre eigentliche Absicht ist, dass dies der "selbe" John Smith ist, und es ist nur so, dass das "Alter" nicht anders ist. Idealerweise möchten Sie nur diesen Array-Eintrag "aktualisieren" und weder ein neues Array noch ein neues Dokument erstellen.

Arbeiten Sie dazu mit .findOneAndUpdate() wohin das aktualisierte Dokument zurückkehren soll, kann schwierig sein. Wenn Sie also nicht wirklich das modifizierte Dokument als Antwort wollen, dann Massenoperations-API von MongoDB und der Kerntreiber sind hier die größte Hilfe.

In Anbetracht der Aussagen:

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

Das ist nett, da die Bulk-Operationen hier bedeuten, dass alle Anweisungen hier auf einmal an den Server gesendet werden und es nur eine Antwort gibt. Beachten Sie hier auch, dass die Logik hier bedeutet, dass höchstens zwei Operationen tatsächlich etwas ändern werden.

In erster Linie der $setOnInsert modifier stellt sicher, dass nichts geändert wird, wenn das Dokument nur eine Übereinstimmung ist. Da die einzigen Änderungen hier innerhalb dieses Blocks stattfinden, betrifft dies nur ein Dokument, in dem ein "Upsert" auftritt.

Beachten Sie auch, dass Sie bei den nächsten beiden Anweisungen nicht versuchen, erneut "upsert". Dies geht davon aus, dass die erste Aussage möglicherweise dort erfolgreich war, wo sie sein musste, oder ansonsten keine Rolle spielte.

Der andere Grund für kein "Upsert" liegt darin, dass die zum Testen des Vorhandenseins des Elements im Array erforderlichen Bedingungen zum "Upsert" eines neuen Dokuments führen würden, wenn sie nicht erfüllt wären. Das ist nicht erwünscht, daher kein "Upsert".

Tatsächlich prüfen sie jeweils, ob das Array-Element vorhanden ist oder nicht, und aktualisieren entweder das vorhandene Element oder erstellen ein neues. Insgesamt bedeuten also alle Operationen, dass Sie entweder "einmal" oder höchstens "zweimal" ändern, falls ein Upsert aufgetreten ist. Das mögliche "zweimal" erzeugt sehr wenig Overhead und kein wirkliches Problem.

Auch in der dritten Anweisung der $not -Operator kehrt die Logik von $elemMatch um festzustellen, dass kein Array-Element mit der Abfragebedingung vorhanden ist.

Übersetzen mit .findOneAndUpdate() wird etwas problematischer. Nicht nur der "Erfolg" zählt jetzt, sondern bestimmt auch, wie der letztendliche Inhalt zurückgegeben wird.

Die beste Idee hier ist also, die Ereignisse in "Serien" auszuführen und dann mit dem Ergebnis ein wenig zu zaubern, um das "aktualisierte" Endformular zurückzugeben.

Die Hilfe, die wir hier verwenden werden, ist sowohl mit async.waterfall und der lodash Bibliothek:

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

Das folgt der gleichen Logik wie zuvor, da nur zwei oder eine dieser Anweisungen tatsächlich etwas mit der Möglichkeit zu tun haben, dass das zurückgegebene "neue" Dokument null sein wird . Der "Wasserfall" gibt hier ein Ergebnis von jeder Stufe zur nächsten weiter, einschließlich des Endes, wo auch jeder Fehler sofort verzweigt.

In diesem Fall der null würde gegen ein leeres Objekt {} ausgetauscht und der _.merge() -Methode kombiniert die beiden Objekte in jedem späteren Stadium zu einem. Dies gibt Ihnen das Endergebnis, das das modifizierte Objekt ist, unabhängig davon, welche vorangegangenen Operationen tatsächlich etwas bewirkt haben.

Natürlich wäre für $pull eine andere Manipulation erforderlich , und auch Ihre Frage hat Eingabedaten als Objektformular an sich. Aber das sind eigentlich Antworten für sich.

Dies sollte Ihnen zumindest den Einstieg in die Herangehensweise an Ihr Aktualisierungsmuster erleichtern.