Algemene reikwijdte en uitleg
Er zijn een paar dingen mis met wat je hier doet. Allereerst uw vraagvoorwaarden. U verwijst naar verschillende _id
waarden waar dat niet zou moeten, en waarvan er tenminste één niet op het hoogste niveau staat.
Om in een "geneste" waarde te komen en ook aan te nemen dat _id
waarde uniek is en in geen enkel ander document zou verschijnen, zou uw vraagformulier er als volgt uit moeten zien:
Model.update(
{ "array1.array2._id": "123" },
{ "$push": { "array1.0.array2.$.answeredBy": "success" } },
function(err,numAffected) {
// something with the result in here
}
);
Nu zou dat echt werken, maar het is eigenlijk maar een toevalstreffer dat het werkt, want er zijn hele goede redenen waarom het niet voor jou zou moeten werken.
De belangrijke lezing staat in de officiële documentatie voor de positionele $
operator onder het onderwerp "Nested Arrays". Wat dit zegt is:
De positionele $-operator kan niet worden gebruikt voor query's die meer dan één array doorkruisen, zoals query's die arrays doorkruisen die in andere arrays zijn genest, omdat de vervanging voor de tijdelijke aanduiding $ een enkele waarde is
Wat dat specifiek betekent, is het element dat wordt vergeleken en geretourneerd in de positionele tijdelijke aanduiding, de waarde van de index van de eerste bijpassende reeks. Dit betekent in uw geval de overeenkomende index op de "top"-array.
Dus als je kijkt naar de query-notatie zoals weergegeven, hebben we de eerste . "hardgecodeerd" ( of 0 index ) positie in de array op het hoogste niveau, en het toeval wil dat het overeenkomende element in "array2" ook de nul-indexinvoer is.
Om dit te demonstreren kunt u de overeenkomende _id
. wijzigen waarde naar "124" en het resultaat zal $push
een nieuwe invoer op het element met _id
"123" aangezien ze beide in de nul-indexinvoer van "array1" staan en dat is de waarde die wordt geretourneerd aan de tijdelijke aanduiding.
Dat is dus het algemene probleem met het nesten van arrays. Je zou een van de niveaus kunnen verwijderen en je zou nog steeds in staat zijn om $push
naar het juiste element in je "top" array, maar er zouden nog steeds meerdere niveaus zijn.
Probeer het nesten van arrays te vermijden, aangezien u updateproblemen zult tegenkomen, zoals wordt getoond.
Het algemene geval is om de dingen waarvan je denkt dat ze "niveaus" zijn "af te vlakken" en in feite scripties "attributen" te maken op de laatste detailitems. De "afgeplatte" vorm van de structuur in de vraag zou bijvoorbeeld zoiets moeten zijn als:
{
"answers": [
{ "by": "success", "type2": "123", "type1": "12" }
]
}
Of zelfs als het accepteren van de binnenste array is $push
alleen, en nooit bijgewerkt:
{
"array": [
{ "type1": "12", "type2": "123", "answeredBy": ["success"] },
{ "type1": "12", "type2": "124", "answeredBy": [] }
]
}
Die zich beide lenen voor atomaire updates in het kader van de positionele $
telefoniste
MongoDB 3.6 en hoger
Vanaf MongoDB 3.6 zijn er nieuwe functies beschikbaar om met geneste arrays te werken. Dit gebruikt de positioneel gefilterde $[<identifier>]
syntaxis om de specifieke elementen te matchen en verschillende voorwaarden toe te passen via arrayFilters
in de updateverklaring:
Model.update(
{
"_id": 1,
"array1": {
"$elemMatch": {
"_id": "12","array2._id": "123"
}
}
},
{
"$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
},
{
"arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }]
}
)
De "arrayFilters"
zoals doorgegeven aan de opties voor .update()
of zelfs.updateOne()
, .updateMany()
, .findOneAndUpdate()
of .bulkWrite()
methode specificeert de voorwaarden die moeten overeenkomen met de identifier die in de update-instructie wordt gegeven. Alle elementen die overeenkomen met de opgegeven voorwaarde zullen worden bijgewerkt.
Omdat de structuur "genest" is, gebruiken we eigenlijk "meerdere filters" zoals gespecificeerd met een "array" van filterdefinities zoals weergegeven. De gemarkeerde "identifier" wordt gebruikt bij het matchen met de positioneel gefilterde $[<identifier>]
syntaxis die daadwerkelijk wordt gebruikt in het updateblok van de instructie. In dit geval inner
en outer
zijn de ID's die worden gebruikt voor elke voorwaarde zoals gespecificeerd met de geneste keten.
Deze nieuwe uitbreiding maakt het updaten van geneste array-inhoud mogelijk, maar het helpt niet echt bij het "opvragen" van dergelijke gegevens, dus gelden dezelfde voorbehouden als eerder uitgelegd.
Normaal gesproken "bedoel je" om het uit te drukken als "attributen", zelfs als je hersenen in eerste instantie "nesten" denken, is het meestal een reactie op hoe je denkt dat de "vorige relationele delen" samenkomen. In werkelijkheid heb je echt meer denormalisatie nodig.
Zie ook Hoe u meerdere array-elementen in mongodb kunt bijwerken, aangezien deze nieuwe update-operators eigenlijk "meerdere array-elementen" matchen en bijwerken in plaats van alleen de eerste , wat de vorige actie van positionele updates was.
OPMERKING Enigszins ironisch, aangezien dit is gespecificeerd in het argument "opties" voor .update()
en net als bij methoden is de syntaxis over het algemeen compatibel met alle recente versies van stuurprogramma's.
Dit geldt echter niet voor de mongo
shell, aangezien de manier waarop de methode daar is geïmplementeerd ("ironisch genoeg voor achterwaartse compatibiliteit") de arrayFilters
argument wordt niet herkend en verwijderd door een interne methode die de opties parseert om "achterwaartse compatibiliteit" te leveren met eerdere MongoDB-serverversies en een "legacy" .update()
API-aanroepsyntaxis.
Dus als je het commando in de mongo
. wilt gebruiken shell of andere "shell-gebaseerde" producten (met name Robo 3T) je hebt een laatste versie nodig van de ontwikkelingstak of de productierelease vanaf 3.6 of hoger.
Zie ook positional all $[]
die ook "meerdere array-elementen" bijwerkt, maar zonder van toepassing te zijn op gespecificeerde voorwaarden en van toepassing is op alle elementen in de array waar dat de gewenste actie is.