MongoDB - Objekte im Array eines Dokuments aktualisieren (verschachtelte Aktualisierung)

204

Angenommen, wir haben die folgende Sammlung, zu der ich einige Fragen habe:

{
    "_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 möchte den Preis für "item_name" erhöhen: "my_item_two". Wenn er nicht vorhanden ist , sollte er an das Array "items" 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 "Summe" (mit demselben Wert).

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

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

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

Aber wenn der Schlüssel nicht existiert, tut er nichts. Außerdem wird nur das verschachtelte Objekt aktualisiert. Mit diesem Befehl kann auch das Feld "total" nicht aktualisiert werden.

Majid
quelle
1
Ich denke, man kann das nicht im Mongo machen, außer vielleicht mit viel Schmerz bei der Auswertung. Mongo ist in Datenoperationen sehr begrenzt.
Antti Haapala
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 das Element nicht existiert, ist ein $ push erforderlich.
Antti Haapala

Antworten:

237

Lassen Sie uns Frage 1 in zwei Teile teilen. Erhöhen Sie zunächst jedes Dokument mit "items.item_name" gleich "my_item_two". 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 dadurch nur das erste übereinstimmende Unterdokument in einem Array inkrementiert wird. Wenn Sie also ein anderes Dokument im Array mit "item_name" gleich "my_item_two" haben, wird es nicht inkrementiert). Aber das könnte sein, was Sie wollen.

Der zweite Teil ist schwieriger. Wir können ein neues Element wie folgt ohne "my_item_two" in ein Array verschieben:

 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 Nr. 2 ist die Antwort einfacher. Um die Summe und den Preis von item_three in einem Dokument zu erhöhen, das "my_item_three" enthält, können Sie den Operator $ inc für mehrere Felder gleichzeitig verwenden. Etwas wie:

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);
matulef
quelle
Danke für die Antwort. Die Antwort auf Frage 2 ist perfekt und funktioniert einwandfrei :) Bei Frage 1 besteht das Problem darin, dass Sie das Dokument nach "user_id" und nicht nach "items.item_name" suchen sollten. In diesem Fall kann ich den Positionsoperator nicht verwenden, da ich das Array in der Suchabfrage nicht angegeben habe. Außerdem möchte ich, dass beide Teile auf einmal erledigt werden. (wenn möglich)
Majid
Schöne Beispiele. Ich hatte das Gefühl, dass ich diese Richtung anhand der Links in meiner Antwort deutlich machen würde, aber ich bekam immer wieder Widerstand und sagte, es sei nicht das, wonach er suchte. Schätze, das OP musste nur Beispiele sehen, wie man mehrere $ inc macht.
JDI
Bei Frage 1 sollten Sie dies weiterhin in zwei Teilen tun können, indem Sie die Benutzer-ID zum Abfragebit jedes Updates hinzufügen (ich werde die Antwort entsprechend ändern). Muss mehr darüber nachdenken, ob es möglich ist, in einem einzigen Schuss zu tun.
Matulef
6
Was sind die 3. und 4. Parameter in der Aktualisierungsmethode? und warum?
Skmahawar
5
@skmahawar bezüglich des 3. und 4. Parameters, docs.mongodb.com/manual/reference/method/db.collection.update, gibt an, dass diese Optionen für "upsert" bzw. "multi" gelten. Wenn Upsert auf true gesetzt ist, wird ein neues Dokument erstellt, wenn kein Dokument den Abfragekriterien entspricht. Der Standardwert ist false, wodurch kein neues Dokument eingefügt wird, wenn keine Übereinstimmung gefunden wird. "Bei Multi werden bei Auswahl von true mehrere Dokumente aktualisiert, die die Abfragekriterien erfüllen. Bei der Einstellung false wird ein Dokument aktualisiert. Der Standardwert ist falsch.
Mike Strand
24

Es gibt keine Möglichkeit, dies in einer einzelnen Abfrage zu tun. Sie müssen das Dokument in der ersten Abfrage durchsuchen:

Wenn ein Dokument vorhanden ist:

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

Sonst

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

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

Auch in Multithread-Umgebungen müssen Sie darauf achten, dass jeweils nur ein Thread den zweiten ausführen kann (Groß- / Kleinschreibung einfügen, wenn das Dokument nicht gefunden wurde). Andernfalls werden doppelte eingebettete Dokumente eingefügt.

Udit
quelle
1
Nein, es gibt eine Möglichkeit, dies in einer einzigen Abfrage zu tun, siehe oben
Martijn Scheffer
1
@MartijnScheffer, ich sehe keine Möglichkeit, dies als einzelne Abfrage irgendwo in diesem Thread zu tun. Sogar die "Antwort" verwendet 2 Abfragen wie in dieser Antwort. Vermisse ich etwas Ich hoffe auch, dass es eine Möglichkeit gibt, dies in einer Abfrage zu tun, um Multithreading-Probleme zu vermeiden
dferraro
@Udit, wie kann ich die Artikel verwenden. $. Preis innerhalb der Bedingung? Wenn ich versuche, eine Bedingung wie "Nur erhöhen, wenn der Preis größer als 0 ist" hinzuzufügen, funktioniert dies nicht - im Grunde wird nichts aktualisiert. Ich kann dies im Wo-Zustand verwenden oder fehlt mir etwas? Artikel. $. Preis: {$ gt: 0}
Dferraro
etwas muss verschwunden sein :)
Martijn Scheffer
3

Wir können den $setOperator verwenden, um das verschachtelte Array innerhalb des abgelegten Objekts zu aktualisieren und den Wert zu aktualisieren

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
KARTHIKEYAN.A
quelle