2012-05-09 12 views
143

Angenommen, wir die folgende Sammlung, die ich einige Fragen zu haben:MongoDB - Update Objekte in einem Dokument des Arrays (verschachtelte Aktualisierung)

{ 
    "_id" : ObjectId("4faaba123412d654fe83hg876"), 
    "user_id" : 123456, 
    "total" : 100, 
    "items" : [ 
      { 
        "item_name" : "my_item_one", 
        "price" : 20 
      }, 
      { 
        "item_name" : "my_item_two", 
        "price" : 50 
      }, 
      { 
        "item_name" : "my_item_three", 
        "price" : 30 
      } 
    ] 
} 

1 - Ich mag für „item_name“, den Preis zu erhöhen:“ my_item_two "und falls es nicht existiert, sollte es an das" items "-Array angehängt werden.

2 - Wie kann ich zwei Felder gleichzeitig aktualisieren? Erhöhen Sie beispielsweise den Preis für "my_item_three" und erhöhen Sie gleichzeitig die "Gesamtmenge" (mit dem gleichen Wert).

Ich bevorzuge dies auf der MongoDB-Seite, andernfalls muss ich das Dokument in Client-Seite (Python) laden und das aktualisierte Dokument erstellen und durch das vorhandene Dokument in MongoDB ersetzen.

UPDATE Dies ist, was ich habe versucht, und funktioniert gut wenn das Objekt vorhanden:

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}}) 

Aber wenn der Schlüssel nicht existiert es tut nichts. Außerdem aktualisiert es nur das verschachtelte Objekt. Mit diesem Befehl gibt es auch keine Möglichkeit, das Feld "total" zu aktualisieren.

+1

Ich denke, Sie können dies nicht in Mongo tun, außer vielleicht mit viel Schmerz mit der Eval. Mongo ist in Datenoperationen sehr eingeschränkt. –

+3

@Haapala: mongodb hat $ inc und update mit upsert – jdi

+1

@jdi ja ja, aber es hilft hier nicht viel, aber was er braucht sind mehrere $ incs, bedingt, und wenn der Artikel nicht existiert, dann ist ein $ push erforderlich. –

Antwort

173

Für Frage # 1, brechen wir es in zwei Teile. Erhöhen Sie zuerst jedes Dokument, das "items.item_name" gleich "my_item_two" hat. Dazu müssen Sie den Positionsoperator "$" verwenden. Etwas wie:

db.bar.update({user_id : 123456 , "items.item_name" : "my_item_two" } , 
       {$inc : {"items.$.price" : 1} } , 
       false , 
       true); 

Beachten Sie, dass dies nur das erste abgestimmte Subdokument in jedem Array erhöhen (also, wenn Sie ein anderes Dokument im Array mit „item_name“ gleich „my_item_two“, wird es nicht bekommen erhöht) . Aber das könnte das sein, was du willst.

Der zweite Teil ist kniffliger. Wir können ein neues Element in ein Array schieben ohne „my_item_two“ wie folgt:

db.bar.update({user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
       {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } , 
       false , 
       true); 

Für Ihre Frage # 2, die Antwort ist einfacher. Um die Summe und den Preis von item_three in jedem Dokument zu erhöhen, das "my_item_three" enthält, können Sie den Operator $ inc für mehrere Felder gleichzeitig verwenden. So etwas wie:

db.bar.update({"items.item_name" : {$ne : "my_item_three" }} , 
       {$inc : {total : 1 , "items.$.price" : 1}} , 
       false , 
       true); 
+0

Danke für die Antwort. Die Antwort auf Frage 2 ist perfekt und funktioniert gut :) Aber für Frage 1, das Problem ist, sollten Sie nach dem Dokument von "user_id" suchen, und nicht von "items.item_name". In diesem Fall kann ich den Positionsoperator nicht verwenden, da ich das Array in der Suchanfrage nicht angegeben habe. Außerdem möchte ich, dass beide Teile auf einmal gemacht werden. (wenn möglich) – Majid

+0

Schöne Beispiele. Ich fühlte mich, als ob ich diese Richtung aus den Links in meiner Antwort ersichtlich machen würde, aber ich bekam immer wieder Widerstand und sagte, dass es nicht das ist, wonach er suchte. Erraten Sie das OP nur benötigt, um Beispiele zu sehen, wie man mehrere $ inc macht. – jdi

+0

Für Frage # 1 sollten Sie es immer noch in zwei Teilen tun können, indem Sie die Benutzer-ID zum Abfrage-Bit jedes Updates hinzufügen (ich werde die Antwort entsprechend modifizieren). Muss mehr darüber nachdenken, ob es in einem einzigen Schuss möglich ist. – matulef

19

Es gibt keine Möglichkeit, dies in einer einzigen Abfrage zu tun. Sie haben das Dokument in der ersten Abfrage suchen:

Wenn Dokument vorhanden ist:

db.bar.update({user_id : 123456 , "items.item_name" : "my_item_two" } , 
       {$inc : {"items.$.price" : 1} } , 
       false , 
       true); 

Else

db.bar.update({user_id : 123456 } , 
       {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } , 
       false , 
       true); 

Keine Notwendigkeit {$ne : "my_item_two" } Bedingung hinzuzufügen.

Auch in Multithread-Umgebung müssen Sie darauf achten, dass nur ein Thread die zweite ausführen kann (Fall einfügen, wenn Dokument nicht gefunden wurde), ansonsten werden doppelte Einbettungsdokumente eingefügt.

+1

nein, es gibt eine Möglichkeit, dies in einer einzigen Abfrage zu tun, siehe oben –

+0

@MartijnScheffer, ich sehe keine Möglichkeit, dies als einzelne Abfrage irgendwo in diesem Thread zu tun. Selbst die "Antwort" verwendet 2 Abfragen, die dieselben wie in dieser Antwort sind. Fehle ich etwas? Ich hoffe auch, es gibt eine Möglichkeit, dies in einer Abfrage zu tun, um Multi Threading Probleme zu verhindern – dferraro

+0

@Udit, wie kann ich die Elemente verwenden. $. Preis in der Bedingung? Wenn ich versuche, eine Bedingung wie "nur erhöhen, wenn der Preis größer als 0 ist" hinzuzufügen, funktioniert es nicht - im Grunde wird nichts aktualisiert. Kann ich das in der Where Condition benutzen, oder fehlt mir etwas? Artikel. $. Preis: {$ gt: 0} – dferraro

Verwandte Themen