sql >> Databáze >  >> NoSQL >> MongoDB

Vložit dokument a/nebo přidat dílčí dokument

Přístup k řešení tohoto problému není jednoduchý, protože míchání „upserts“ s přidáváním položek do „polí“ může snadno vést k nežádoucím výsledkům. Záleží také na tom, zda chcete, aby logika nastavila další pole, jako je "počítadlo" udávající, kolik kontaktů je v poli, které chcete pouze zvyšovat/snižovat při přidávání nebo odebírání položek.

V nejjednodušším případě by však „kontakty“ obsahovaly pouze singulární hodnotu, jako je ObjectId odkazující na jinou kolekci, pak $addToSet modifikátor funguje dobře, pokud nejsou zapojeny žádné "počítače":

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

A to je vše v pořádku, protože pouze testujete, zda dokument odpovídá názvu "clientName", ne-li ho upsert. Ať už existuje shoda nebo ne, $addToSet operátor se postará o jedinečné „singulární“ hodnoty, což je jakýkoli „objekt“, který je skutečně jedinečný.

Potíže přicházejí tam, kde máte něco jako:

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

Již jste v poli kontaktů a pak chcete udělat něco takového:

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

Kde je vaším skutečným záměrem, že se jedná o „stejného“ Johna Smithe, a pouze ten „věk“ se neliší. V ideálním případě chcete pouze "aktualizovat" tuto položku pole nebo vytvořit nové pole nebo nový dokument.

Funguje to pomocí .findOneAndUpdate() kam chcete, aby se aktualizovaný dokument vrátil, může být obtížné. Pokud tedy ve skutečnosti nechcete upravený dokument jako odpověď, pak Rozhraní API pro hromadné operace Zde nejvíce pomáhají MongoDB a základní ovladač.

Vzhledem k výrokům:

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

To je hezké, protože hromadné operace zde znamenají, že všechny výpisy zde jsou odeslány na server najednou a existuje pouze jedna odpověď. Zde si také povšimněte, že logika zde znamená, že maximálně dvě operace skutečně něco upraví.

V prvním případě $setOnInsert modifikátor zajišťuje, že se nic nezmění, když je dokument pouze shodný. Protože jediné úpravy jsou v tomto bloku, ovlivní to pouze dokument, kde se vyskytuje "upsert".

Všimněte si také u následujících dvou tvrzení, která se již nepokoušejte znovu „narušit“. To má za to, že první prohlášení bylo možná úspěšné tam, kde mělo být, nebo na tom jinak nezáleželo.

Dalším důvodem pro žádný "upsert" je to, že podmínky potřebné k testování přítomnosti prvku v poli by vedly k "upsert" nového dokumentu, pokud by nebyly splněny. To není žádoucí, proto žádný "upsert".

Ve skutečnosti zkontrolují, zda je prvek pole přítomen nebo ne, a buď aktualizují existující prvek, nebo vytvoří nový. Celkově tedy všechny operace znamenají, že v případě, že došlo k upsert, buď upravíte „jednou“ nebo maximálně „dvakrát“. Možné „dvakrát“ vytváří velmi malou režii a žádný skutečný problém.

Také ve třetím prohlášení $not operátor obrací logiku $elemMatch abyste zjistili, že neexistuje žádný prvek pole s podmínkou dotazu.

Překlad pomocí .findOneAndUpdate() se stává trochu větším problémem. Nejen, že na „úspěchu“ nyní záleží, ale také určuje, jak bude případný obsah vrácen.

Nejlepším nápadem je tedy spustit události v "sériích" a poté trochu kouzlit s výsledkem, aby se vrátil konečný "aktualizovaný" formulář.

Nápověda, kterou zde použijeme, je jak s async.waterfall a lodash knihovna:

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

To se řídí stejnou logikou jako dříve v tom, že pouze dva nebo jeden z těchto příkazů skutečně udělají cokoli s možností, že vrácený "nový" dokument bude null . "Vodopád" zde předává výsledek z každé fáze do další, včetně konce, kam se také jakákoli chyba okamžitě rozvětví.

V tomto případě null by byl zaměněn za prázdný objekt {} a _.merge() metoda zkombinuje dva objekty do jednoho, v každé pozdější fázi. To vám dává konečný výsledek, kterým je upravený objekt, bez ohledu na to, které předchozí operace skutečně něco provedly.

Pro $pull by samozřejmě byla nutná jiná manipulace , a také vaše otázka obsahuje vstupní data jako objektový formulář sám o sobě. Ale to jsou ve skutečnosti odpovědi samy o sobě.

To by vám mělo alespoň začít s tím, jak přistupovat k vašemu vzoru aktualizace.



  1. Mongo pole A větší než pole B

  2. Skript pro odstranění zástupných znaků Redis pomocí EVAL, SCAN a DEL vrací příkazy zápisu, které nejsou povoleny po nedeterministických příkazech

  3. mongoDB:jak zkontrolovat existenci diktovacího klíče ve vnořeném diktátu?

  4. Vkládání dat do MongoDB - žádná chyba, žádné vkládání