MongoDB 3.6 i nowsze
W MongoDB 3.6 i nowszych dostępna jest nowa funkcja, która pozwala aktualizować zagnieżdżone tablice za pomocą filtrowanego pozycyjnie $\[<identifier>\]
składnia w celu dopasowania określonych elementów i zastosowania różnych warunków za pomocą arrayFilters
w oświadczeniu o aktualizacji:
const { oid, pid } = req.params;
const { name, oName, description, type } = req.body;
collection.update(
{
"_id": 1,
"operations": {
"$elemMatch": {
oid, "parameters.pid": pid
}
}
},
{ "$set": {
"operations.$[outer].parameters.$[inner].name": name,
"operations.$[outer].parameters.$[inner].description": description,
"operations.$[outer].parameters.$[inner].oName": oName,
"operations.$[outer].parameters.$[inner].type": type
} },
{ "arrayFilters": [
{ "outer.oid": oid },
{ "inner.pid": pid }
] }, (err, result) => {
if (err) {
console.log('Error updating service: ' + err);
res.send({'error':'An error has occurred'});
} else {
// console.log('' + result + ' document(s) updated');
res.send(result);
}
});
Dla MongoDB 3.4 i starszych:
Jak @wdberkeley wspomniał w swoim komentarzu:
MongoDB nie obsługuje dopasowywania do więcej niż jednego poziomu tablicy. Rozważ zmianę modelu dokumentu tak, aby każdy dokument reprezentował operację z informacjami wspólnymi dla zestawu operacji zduplikowanych w dokumentach operacyjnych.
Zgadzam się z powyższym i zalecam przeprojektowanie schematu, ponieważ silnik MongoDB nie obsługuje wielu operatorów pozycyjnych (Patrz Wielokrotne użycie pozycyjnego $
operator do aktualizacji zagnieżdżonych tablic )
Jeśli jednak znasz indeks tablicy operacji, która zawiera obiekt parametrów, który ma zostać zaktualizowany, zapytanie aktualizujące będzie wyglądało następująco:
db.collection.update(
{
"_id" : "04",
"operations.parameters.pid": "011"
},
{
"$set": {
"operations.0.parameters.$.name": "foo",
"operations.0.parameters.$.description": "bar",
"operations.0.parameters.$.type": "foo"
}
}
)
EDYTUJ:
Jeśli chcesz utworzyć $set
warunki w locie, tj. coś, co pomogłoby uzyskać indeksy obiektów, a następnie odpowiednio je zmodyfikować, a następnie rozważ użycie MapReduce .
Obecnie wydaje się, że nie jest to możliwe przy użyciu struktury agregacji. Istnieje nierozwiązany otwarty problem z JIRA powiązane z nim. Możliwe jest jednak obejście tego problemu dzięki MapReduce . Podstawową ideą MapReduce jest to, że używa JavaScript jako języka zapytań, ale jest to dość wolniejsze niż struktura agregacji i nie powinno być używane do analizy danych w czasie rzeczywistym.
W swojej operacji MapReduce musisz zdefiniować kilka kroków, tj. krok mapowania (który mapuje operację do każdego dokumentu w kolekcji, a operacja może albo nic nie robić, albo emitować jakiś obiekt z kluczami i przewidywanymi wartościami) i krok redukujący ( który pobiera listę emitowanych wartości i redukuje ją do jednego elementu).
W przypadku kroku mapy najlepiej byłoby uzyskać dla każdego dokumentu w kolekcji indeks dla każdej operations
pole tablicy i inny klucz zawierający $set
klawisze.
Krokiem redukcji będzie funkcja (która nic nie robi) zdefiniowana po prostu jako var reduce = function() {};
Ostatnim krokiem w operacji MapReduce będzie utworzenie oddzielnej operacji zbierania, która zawiera obiekt tablicy emitowanych operacji wraz z polem z $set
warunki. Ta kolekcja może być okresowo aktualizowana po uruchomieniu operacji MapReduce na oryginalnej kolekcji. W sumie ta metoda MapReduce wyglądałaby następująco:
var map = function(){
for(var i = 0; i < this.operations.length; i++){
emit(
{
"_id": this._id,
"index": i
},
{
"index": i,
"operations": this.operations[i],
"update": {
"name": "operations." + i.toString() + ".parameters.$.name",
"description": "operations." + i.toString() + ".parameters.$.description",
"type": "operations." + i.toString() + ".parameters.$.type"
}
}
);
}
};
var reduce = function(){};
db.collection.mapReduce(
map,
reduce,
{
"out": {
"replace": "operations"
}
}
);
Zapytanie o zbiór danych wyjściowych operations
z operacji MapReduce zazwyczaj daje wynik:
db.operations.findOne()
Wyjście :
{
"_id" : {
"_id" : "03",
"index" : 0
},
"value" : {
"index" : 0,
"operations" : {
"_id" : "96",
"oName" : "test op 52222222222",
"sid" : "04",
"name" : "test op 52222222222",
"oid" : "99",
"description" : "testing",
"returntype" : "test",
"parameters" : [
{
"oName" : "Param1",
"name" : "foo",
"pid" : "011",
"type" : "foo",
"description" : "bar",
"value" : ""
},
{
"oName" : "Param2",
"name" : "Param2",
"pid" : "012",
"type" : "58222",
"description" : "testing",
"value" : ""
}
]
},
"update" : {
"name" : "operations.0.parameters.$.name",
"description" : "operations.0.parameters.$.description",
"type" : "operations.0.parameters.$.type"
}
}
}
Następnie możesz użyć kursora z db.operations.find()
metoda iteracji i odpowiedniej aktualizacji kolekcji:
var oid = req.params.operations;
var pid = req.params.parameters;
var cur = db.operations.find({"_id._id": oid, "value.operations.parameters.pid": pid });
// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
var doc = cur.next();
var update = { "$set": {} };
// set the update query object
update["$set"][doc.value.update.name] = req.body.name;
update["$set"][doc.value.update.description] = req.body.description;
update["$set"][doc.value.update.type] = req.body.type;
db.collection.update(
{
"_id" : oid,
"operations.parameters.pid": pid
},
update
);
};