2014-05-05 27 views
24

Mam dokumenty, które wygląda mniej więcej tak, że z unikatowego indeksu na bars.name:MongoDB: upsert sub-dokumentów

{ name: 'foo', bars: [ { name: 'qux', somefield: 1 } ] }

. Chcę zaktualizować sub-dokument, gdzie { name: 'foo', 'bars.name': 'qux' } i $set: { 'bars.$.somefield': 2 }, lub utworzyć nowy sub-dokument z { name: 'qux', somefield: 2 } pod { name: 'foo' }.

Czy można to zrobić za pomocą pojedynczego zapytania z upsert, czy będę musiał wydać dwa osobne?

pokrewne: 'upsert' in an embedded document (sugeruje zmianę schematu mieć identyfikator sub-dokument jako klucz, ale to ze dwa lata temu i zastanawiam się, czy istnieją lepsze rozwiązania teraz).

Odpowiedz

32

Nie istnieje nie jest tak naprawdę lepszym rozwiązaniem, więc może z wyjaśnieniem.

Załóżmy, że mamy dokument w miejscu, które ma strukturę jak pokazać:

{ 
    "name": "foo", 
    "bars": [{ 
     "name": "qux", 
     "somefield": 1 
    }] 
} 

Jeśli robisz update jak to

db.foo.update(
    { "name": "foo", "bars.name": "qux" }, 
    { "$set": { "bars.$.somefield": 2 } }, 
    { "upsert": true } 
) 

Wtedy wszystko jest w porządku, ponieważ dopasowanie dokument był uznany. Ale jeśli zmienisz wartość "bars.name":

db.foo.update(
    { "name": "foo", "bars.name": "xyz" }, 
    { "$set": { "bars.$.somefield": 2 } }, 
    { "upsert": true } 
) 

Wtedy otrzymasz błąd. Jedyną rzeczą, która zmieniła się o to, że w MongoDB 2.6 i wyżej błędu jest nieco bardziej zwięzła:

WriteResult({ 
    "nMatched" : 0, 
    "nUpserted" : 0, 
    "nModified" : 0, 
    "writeError" : { 
     "code" : 16836, 
     "errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: bars.$.somefield" 
    } 
}) 

To jest lepsze pod pewnymi względami, ale naprawdę nie chce „upsert” tak. To, co chcesz zrobić, to dodać element do tablicy, w której "nazwa" obecnie nie istnieje.

Więc co naprawdę chcesz to „wynik” z próbą aktualizację bez flagi „upsert” aby sprawdzić, czy zostały naruszone jakiekolwiek dokumenty:

db.foo.update(
    { "name": "foo", "bars.name": "xyz" }, 
    { "$set": { "bars.$.somefield": 2 } } 
) 

Plonowanie w odpowiedzi:

WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 }) 

Jeśli więc zmodyfikowane dokumenty to 0, to wiesz, że chcesz wydać następującą aktualizację:

db.foo.update(
    { "name": "foo" }, 
    { "$push": { "bars": { 
     "name": "xyz", 
     "somefield": 2 
    }} 
) 

Naprawdę nie ma innego sposobu, aby zrobić dokładnie to, co chcesz. Ponieważ dodatki do tablicy nie są ściśle "ustawionym" typem operacji, nie można użyć funkcji $addToSet w połączeniu z funkcjonalnością "bulk update", dzięki czemu można "kaskadować" żądania aktualizacji.

W tym przypadku wydaje się, że trzeba sprawdzić wynik lub w inny sposób zaakceptować czytanie całego dokumentu i sprawdzanie, czy zaktualizować lub wstawić nowy element tablicy w kodzie.

+2

Tak, to właśnie mam obecnie - '$ set', po którym następuje' $ push', jeśli nie znaleziono pasujących dokumentów ... Zastanawiałem się, czy istnieje lepszy sposób na zrobienie tego. Dziękuję za wyjaśnienie! – shesek

+5

Jak zrobić to atomowe, jeśli masz wiele procesów próbujących aktualizować w tym samym czasie. Wygląda na to, że może wystąpić warunek wyścigowy i możesz wywołać funkcję $ push więcej niż jeden raz, a skończy się na więcej niż jednym rekordzie w tablicy. Czy istnieje sposób na wstawienie nowej wartości w sposób atomowy? –

+7

@MichaelMoser Dopiero co zobaczyłem komentarz. Ale dodając test nierówności typu '{" name ":" foo "," bars.name ": {" $ ne ":" xyz "}}' jako zapytanie, upewniasz się, że nie powielasz elementów "pushed" . –

1

jeśli nie zmieniając schematu trochę i mający strukturę jak więc pamiętać:

{ "name": "foo", "bars": { "qux": { "somefield": 1 }, 
          "xyz": { "somefield": 2 }, 
        } 
} 

można wykonywać operacje w jednym zamachem. Powtórzenie 'upsert' in an embedded document dla kompletności

0

Nie można tego zrobić, jeśli aktualizacja opiera się na odwołaniu do aktualizowanego rekordu (np. Aktualizacja x => x + 1). Wydanie 2 osobnych komend (ustawiaj i wstawiaj) powoduje, że warunki wyścigu nie mogą zostać rozwiązane przez sprawdzenie duplikatów, ponieważ jeśli twoja wstawka zostanie odrzucona z powodu duplikatu, stracisz efekt aktualizacji (np. X nie być odpowiednio zwiększane w powyższym przykładzie). Byłoby miło, gdyby MongoDB dodał tę funkcjonalność, ponieważ zdolność do implementacji dokumentów osadzonych jest dużym pociągnięciem do MongoDB w przypadku niektórych aplikacji.