MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Wstaw dokument i/lub dodaj poddokument

Podejście do radzenia sobie z tym nie jest proste, ponieważ mieszanie „upsertów” z dodawaniem elementów do „tablic” może łatwo prowadzić do niepożądanych rezultatów. Zależy to również od tego, czy logika ma ustawić inne pola, takie jak „licznik” wskazujący, ile kontaktów znajduje się w tablicy, które chcesz tylko zwiększać/zmniejszać, gdy elementy są odpowiednio dodawane lub usuwane.

Jednak w najprostszym przypadku, jeśli "kontakty" zawierały tylko pojedynczą wartość, taką jak ObjectId linkowanie do innej kolekcji, a następnie $addToSet modyfikator działa dobrze, o ile nie ma żadnych „liczników”:

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

I wszystko jest w porządku, ponieważ testujesz tylko, aby sprawdzić, czy dokument pasuje do "nazwa klienta", jeśli nie, podmień go. Niezależnie od tego, czy pasuje, czy nie, $addToSet operator zadba o unikalne „pojedyncze” wartości, będące dowolnym „obiektem”, który jest naprawdę wyjątkowy.

Trudności pojawiają się, gdy masz coś takiego:

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

Już w tablicy kontaktów, a następnie chcesz zrobić coś takiego:

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

W przypadku, gdy twoją rzeczywistą intencją jest to, że jest to „ten sam” John Smith, a po prostu „wiek” nie jest inny. Najlepiej byłoby po prostu „zaktualizować” ten wpis w tablicy, nie tworząc nowej tablicy lub nowego dokumentu.

Praca z .findOneAndUpdate() miejsce, w którym chcesz zwrócić zaktualizowany dokument, może być trudne. Więc jeśli naprawdę nie chcesz, aby zmodyfikowany dokument w odpowiedzi, to Interfejs API operacji zbiorczych MongoDB i główny sterownik są tutaj najbardziej pomocne.

Biorąc pod uwagę wypowiedzi:

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

Jest to miłe, ponieważ operacje zbiorcze oznaczają, że wszystkie oświadczenia są wysyłane do serwera jednocześnie i jest tylko jedna odpowiedź. Zauważ również, że logika oznacza tutaj, że co najwyżej dwie operacje faktycznie zmodyfikują cokolwiek.

W pierwszym przypadku $setOnInsert modyfikator zapewnia, że ​​nic nie zostanie zmienione, gdy dokument jest tylko dopasowaniem. Ponieważ jedyne modyfikacje znajdują się w tym bloku, dotyczy to tylko dokumentu, w którym występuje „upsert”.

Zwróć też uwagę na kolejne dwa stwierdzenia, których nie próbujesz ponownie „przewracać”. Uważa to, że pierwsze stwierdzenie prawdopodobnie odniosło sukces tam, gdzie musiało być, lub w inny sposób nie miało znaczenia.

Innym powodem braku „upsert” jest to, że warunki potrzebne do przetestowania obecności elementu w tablicy doprowadziłyby do „upsert” nowego dokumentu, gdy nie zostałyby spełnione. To nie jest pożądane, dlatego nie ma „przewracania się”.

W rzeczywistości sprawdzają odpowiednio, czy element tablicy jest obecny, czy nie, i aktualizują istniejący element lub tworzą nowy. Dlatego w sumie wszystkie operacje oznaczają, że modyfikujesz „raz” lub co najwyżej „dwukrotnie” w przypadku wystąpienia upsert. Ewentualne „dwukrotne” powoduje bardzo niewielkie obciążenie i nie ma prawdziwego problemu.

Również w trzecim stwierdzeniu $not operator odwraca logikę $elemMatch aby ustalić, że nie istnieje żaden element tablicy z warunkiem zapytania.

Tłumaczenie tego za pomocą .findOneAndUpdate() staje się nieco większym problemem. Teraz liczy się nie tylko „sukces”, ale także sposób, w jaki zostanie zwrócona ostateczna treść.

Najlepszym pomysłem jest więc uruchomienie wydarzeń w „seriach”, a następnie trochę magii z wynikiem, aby zwrócić końcowy „zaktualizowany” formularz.

Pomoc, której tutaj użyjemy, dotyczy zarówno async.waterfall i lodash biblioteka:

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

Jest to zgodne z tą samą logiką, co poprzednio, ponieważ tylko dwie lub jedna z tych instrukcji faktycznie zrobi cokolwiek z możliwością, że zwrócony "nowy" dokument będzie miał wartość null . Tutaj „wodospad” przekazuje wynik z każdego etapu do następnego, w tym do końca, do którego również każdy błąd natychmiast się rozgałęzia.

W tym przypadku null zostanie zamieniony na pusty obiekt {} i _.merge() Metoda połączy oba obiekty w jeden, na każdym późniejszym etapie. Daje to końcowy wynik, którym jest zmodyfikowany obiekt, bez względu na to, które poprzednie operacje faktycznie cokolwiek zrobiły.

Oczywiście wymagana byłaby inna manipulacja dla $pull , a także Twoje pytanie zawiera dane wejściowe jako formę obiektu. Ale to są właściwie odpowiedzi same w sobie.

Powinno to przynajmniej pomóc w podejściu do wzorca aktualizacji.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Jak przekazać zmienną środowiskową do skryptu mongo?

  2. Jak mogę uruchamiać polecenia MongoDB, odpytując specjalną kolekcję $cmd?

  3. Wypełnianie odnośnika w schemacie mangusty podczas pracy z Graphql

  4. Policz zarówno zewnętrzną, jak i wewnętrzną tablicę osadzoną w jednym zapytaniu

  5. Uzyskiwanie zduplikowanych wierszy przy lewym sprzężeniu w raportach Birta