sql >> Databasteknik >  >> NoSQL >> MongoDB

Uppdatera en kapslad array med MongoDB

Allmän omfattning och förklaring

Det är några saker som är fel med det du gör här. För det första dina frågevillkor. Du hänvisar till flera _id värden där du inte borde behöva, och varav åtminstone en inte är på toppnivån.

För att komma in i ett "kapslat" värde och även förutsatt att _id värdet är unikt och skulle inte förekomma i något annat dokument, ditt frågeformulär ska vara så här:

Model.update(
    { "array1.array2._id": "123" },
    { "$push": { "array1.0.array2.$.answeredBy": "success" } },
    function(err,numAffected) {
       // something with the result in here
    }
);

Nu skulle det faktiskt fungera, men egentligen är det bara en slump att det gör det eftersom det finns mycket goda skäl till varför det inte borde fungera för dig.

Den viktiga läsningen finns i den officiella dokumentationen för den positionella $ operatör under ämnet "Nested Arrays". Vad detta säger är:

Den positionella $-operatorn kan inte användas för frågor som går igenom mer än en array, till exempel frågor som korsar arrayer kapslade i andra arrayer, eftersom ersättningen för $-platshållaren är ett enda värde

Specifikt vad det betyder är att elementet som kommer att matchas och returneras i den positionella platshållaren är värdet på indexet från den första matchande array. Detta betyder i ditt fall det matchande indexet på den "översta" nivån.

Så om du tittar på frågenotationen som visas, har vi "hårdkodat" den första ( eller 0 index ) position i den översta nivån, och det råkar bara råka ut för att det matchande elementet inom "array2" också är nollindexposten.

För att visa detta kan du ändra det matchande _id värde till "124" och resultatet kommer att $push en ny post i elementet med _id "123" eftersom de båda finns i nollindexposten för "array1" och det är värdet som returneras till platshållaren.

Så det är det allmänna problemet med kapslingar. Du kan ta bort en av nivåerna och du skulle fortfarande kunna $push till rätt element i din "topp"-array, men det skulle fortfarande finnas flera nivåer.

Försök att undvika kapslingar eftersom du kommer att stöta på uppdateringsproblem som visas.

Det allmänna fallet är att "platta ut" de saker du "tror" är "nivåer" och faktiskt göra dessa "attribut" på de sista detaljobjekten. Till exempel bör den "tillplattade" formen av strukturen i frågan vara något som:

 {
   "answers": [
     { "by": "success", "type2": "123", "type1": "12" }
   ]
 }

Eller till och med när den inre arrayen accepteras är $push endast och aldrig uppdaterad:

 {
   "array": [
     { "type1": "12", "type2": "123", "answeredBy": ["success"] },
     { "type1": "12", "type2": "124", "answeredBy": [] }
   ]
 }

Som båda lämpar sig för atomära uppdateringar inom ramen för den positionella $ operatör

MongoDB 3.6 och senare

Från MongoDB 3.6 finns det nya funktioner tillgängliga för att arbeta med kapslade arrayer. Detta använder den positionsfiltrerade $[<identifier>] syntax för att matcha de specifika elementen och tillämpa olika villkor genom arrayFilters i uppdateringsmeddelandet:

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

"arrayFilters" som skickas till alternativen för .update() eller till och med.updateOne() , .updateMany() , .findOneAndUpdate() eller .bulkWrite() metod specificerar villkoren för att matcha på identifieraren som anges i uppdateringssatsen. Alla element som matchar det angivna villkoret kommer att uppdateras.

Eftersom strukturen är "kapslad" använder vi faktiskt "flera filter" som specificeras med en "array" av filterdefinitioner som visas. Den markerade "identifieraren" används vid matchning mot den positionsfiltrerade $[<identifier>] syntax som faktiskt används i programsatsens uppdateringsblock. I det här fallet inner och outer är identifierarna som används för varje villkor som specificerats med den kapslade kedjan.

Den här nya expansionen gör uppdatering av kapslad array-innehåll möjlig, men det hjälper inte riktigt med det praktiska att "fråga" sådan data, så samma varningar gäller som förklarats tidigare.

Du typiskt "menar" verkligen att uttrycka som "attribut", även om din hjärna från början tänker "nesting", är det bara vanligtvis en reaktion på hur du tror att de "tidigare relationsdelarna" kommer samman. I verkligheten behöver du verkligen mer denormalisering.

Se även Hur man uppdaterar flera matriselement i mongodb, eftersom dessa nya uppdateringsoperatörer faktiskt matchar och uppdaterar "flera matriselement" snarare än bara de första , som har varit den tidigare åtgärden för positionsuppdateringar.

OBS Något ironiskt, eftersom detta anges i "alternativ"-argumentet för .update() och liknande metoder är syntaxen i allmänhet kompatibel med alla senaste versioner av drivrutiner.

Detta är dock inte sant för mongo skal, eftersom metoden är implementerad där ("ironiskt nog för bakåtkompatibilitet") arrayFilters argumentet känns inte igen och tas bort av en intern metod som analyserar alternativen för att leverera "bakåtkompatibilitet" med tidigare MongoDB-serverversioner och en "legacy" .update() API-anropssyntax.

Så om du vill använda kommandot i mongo skal eller andra "skalbaserade" produkter (särskilt Robo 3T) behöver du en senaste version från antingen utvecklingsgrenen eller produktionsversionen från och med 3.6 eller senare.

Se även positional all $[] som också uppdaterar "multiple array elements" men utan att gälla specificerade villkor och gäller alla element i arrayen där det är den önskade åtgärden.



  1. MongoDB mongoose utfasningsvarning

  2. jackson avserialisera objekt med lista över vårens gränssnitt

  3. Redis och frågevärden

  4. Java Mongodb nummerlång fråga