2014-05-05 17 views
24

Ich habe Dokumente, die auf bars.name so etwas, mit einem eindeutigen Index aussieht:MongoDB: Upsert Unter Dokument

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

. Ich möchte entweder das Unterdokument mit { name: 'foo', 'bars.name': 'qux' } und $set: { 'bars.$.somefield': 2 } aktualisieren oder ein neues Unterdokument mit { name: 'qux', somefield: 2 } unter { name: 'foo' } erstellen.

Ist es möglich, dies mit einer einzigen Abfrage mit Upsert zu tun, oder muss ich zwei separate ausstellen?

Verwandte: 'upsert' in an embedded document (schlagen das Schema zu ändern, um die Unterdokumentkennung als Schlüssel zu haben, aber das ist von vor zwei Jahren und ich frage mich, ob es jetzt bessere Lösungen.)

Antwort

32

Nein, es ist nicht wirklich eine bessere Lösung, also vielleicht mit einer Erklärung.

Angenommen, Sie ein Dokument an der richtigen Stelle, die die Struktur hat, wie Sie zeigen:

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

Wenn Sie das tun ein Update wie dieses

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

Dann alles, weil passende Dokument in Ordnung war gefunden. Aber wenn Sie den Wert von "bars.name" ändern:

Dann erhalten Sie einen Fehler. Das einzige, was hier wirklich geändert hat, ist, dass in MongoDB 2.6 und über dem Fehler etwas prägnanter ist:

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

, die besser in gewisser Weise, aber Sie wollen wirklich nicht sowieso „Upsert“. Was Sie tun möchten, ist das Element zu dem Array hinzufügen, wo der "Name" derzeit nicht existiert.

Also, was Sie wirklich wollen, ist das „Ergebnis“ aus dem Update-Versuch ohne die „Upsert“ Flagge zu sehen, ob irgendwelche Dokumente betroffen waren:

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

in Reaktion Nachgeben:

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

Also, wenn die geänderten Dokumente sind 0 dann wissen Sie, Sie das folgende Update ausgegeben werden soll:

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

Es gibt wirklich keinen anderen Weg, genau das zu tun, was Sie wollen. Da es sich bei den Hinzufügungen zum Array nicht unbedingt um einen Operationstyp "set" handelt, können Sie $addToSet in Kombination mit der "bulk update"-Funktionalität nicht verwenden, sodass Sie Ihre Aktualisierungsanforderungen "kaskadieren" können.

In diesem Fall scheint es, als ob Sie das Ergebnis überprüfen müssen, oder andernfalls akzeptieren Sie das Lesen des gesamten Dokuments und überprüfen, ob ein neues Array-Element im Code zu aktualisieren oder einzufügen.

+2

Ja, das ist, was ich gerade habe - '$ set', gefolgt von einem' $ push', wenn keine passenden Dokumente gefunden wurden ... Ich habe mich nur gefragt, ob es einen besseren Weg gibt. Danke für die Erklärung! – shesek

+5

Wie machen Sie das atomar, wenn Sie mehrere Prozesse gleichzeitig versuchen zu aktualisieren. Scheint so, als könnte es eine Race Condition geben und du könntest $ push mehr als einmal aufrufen und am Ende mehr als einen Record im Array haben. Gibt es eine Möglichkeit, den neuen Wert auf atomare Weise einzufügen? –

+7

@MichaelMoser Nur gerade den Kommentar gesehen. Aber indem Sie einen Ungleichungstest wie '{" name ":" foo "," bars.name ": {" $ ne ":" xyz "}}' als Abfrage hinzufügen, stellen Sie sicher, dass Sie keine "gepushten" Elemente duplizieren . –

1

wenn es Ihnen nichts ausmacht, das Schema ein wenig verändert und eine Struktur wie so mit:

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

Sie können Ihre Operationen in einem Rutsch durchzuführen. Wiederholen der 'upsert' in an embedded document für die Vollständigkeit

0

Es gibt keine Möglichkeit, dies zu tun, wenn die Aktualisierung auf einen Verweis auf den Datensatz basiert, der aktualisiert wird (z. B. Update x => x + 1). Das Ausgeben von zwei separaten Befehlen (Setzen und Einfügen) führt zu einer Race-Bedingung, die nicht durch Duplikatprüfung gelöst werden kann. Wenn Ihr Insert aufgrund eines Duplikats abgelehnt wird, verlieren Sie den Effekt Ihres Updates (z. B. x nicht) im obigen Beispiel richtig inkrementiert werden). Es wäre schön, wenn MongoDB diese Funktionalität hinzufügen würde, da die Möglichkeit, eingebettete Dokumente zu implementieren, für bestimmte Anwendungen ein großer Vorteil für MongoDB ist.

Verwandte Themen