sql >> Database teknologi >  >> NoSQL >> MongoDB

Masseopdatering af matchende underdokument i Mongodb

I det korteste svar er det både "ja" og "nej".

Der er faktisk en måde at matche individuelle matrixelementer og opdatere dem med separate værdier i en enkelt sætning, da du faktisk kan levere "flere" arrayFilters betingelser og brug disse identifikatorer i din opdateringserklæring.

Problemet med din specifikke prøve her er, at en af ​​posterne i dit "ændringssæt" (den sidste) faktisk ikke matcher noget array-medlem, der er til stede i øjeblikket. Den "formodede" handling her ville være at $push det nye ikke-matchede medlem ind i arrayet, hvor det ikke blev fundet. Men den særlige handling kan ikke gøres i en "enkelt operation" , men du kan bruge bulkWrite() at udstede "flere" erklæringer for at dække den sag.

Matching af forskellige array-betingelser

Forklar det i punkter, overvej de to første elementer i dit "skiftesæt". Du kan anvende en "single" opdater sætning med flere arrayFilters sådan her:

db.avail_rates_copy.updateOne( { "_id":12345 }, { "$set":{ "rates.$[one]":{ "productId" :NumberInt(1234), "rate" :400.0, "rateCardId":NumberInt(1), "month" :NumberInt(201801) }, "rates.$[two]":{ "productId" :NumberInt(1234), "rate" :500.0, "rateCardId":NumberInt(1), "month" :NumberInt(201802) } } }, { "arrayFilters":[ { "one.productId":NumberInt(1234), "one.rateCardId":NumberInt(1), "one.month ":NumberInt(201801) }, { "two.productId":NumberInt(1234), "two.rateCardId":NumberInt(1), "two.month":NumberInt(201802) } ] }) 

Hvis du kørte, ville du se det ændrede dokument bliver:

{ "_id" :12345, "_class" :"com.example.ProductRates", "rates" :[ {// Matchede og ændrede dette med ét "productId" :1234, "rate" :400 , "rateCardId" :1, "month" :201801 }, { // Og dette som to "productId" :1234, "rate" :500, "rateCardId" :1, "month" :201802 }, { "productId" :1234, "rate" :400, "rateCardId" :2, "month" :201803 }, { "productId" :1235, "rate" :500, "rateCardId" :1, "month" :201801 } { "productId" :1235, "rate" :234, "rateCardId" :2, "month" :201803 } ]} 

Bemærk her, at du angiver hver "identfier" på listen over arrayFilters med flere betingelser for at matche elementet som sådan:

 { "one.productId":NumberInt(1234), "one.rateCardId":NumberInt(1), "one.month":NumberInt(201801) }, 

Så hver "tilstand" kortlægges effektivt som:

.

Så det ved at se på "priserne" array efter sætningen i opdateringsblokken af ​​$[] :

 "rates.$[one]" 

Og ser på hvert element i "rater" at matche betingelserne. Så "en" identifikator ville matche betingelserne foran med "one" og på samme måde for det andet sæt betingelser præfikset med "to" , derfor gælder den faktiske opdateringssætning kun for dem, der matcher de betingelser, der er tildelt identifikatoren.

Hvis du bare ville have "priserne" egenskab i modsætning til hele objektet, så noterer du bare som:

{ "$set":{ "rates.$[one].rate":400, "rates.$[two].rate":500 } } 

Tilføjelse af ikke-matchede objekter

Så den første del er forholdsvis enkel at forstå, men som nævnt laver man en code>$push for "elementet som ikke er der" er en anden sag, da vi grundlæggende har brug for en forespørgselsbetingelse på "dokument"-niveauet for at bestemme, at et array-element "mangler".

Hvad dette i bund og grund betyder er, at du skal udstede en opdatering med $push leder efter hvert array-element for at se, om det eksisterer eller ej. Når det ikke er til stede, er dokumentet et match og $push udføres.

Det er her bulkWrite() kommer i spil, og du bruger det ved at tilføje en ekstra opdatering til vores første operation ovenfor for hvert element i "ændringssættet":

db.avail_rates_copy.bulkWrite( [ { "updateOne":{ "filter":{ "_id":12345 }, "update":{ "$set":{ "rates.$[one]" :{ "productId" :NumberInt(1234), "rate" :400.0, "rateCardId":NumberInt(1), "month" :NumberInt(201801) }, "rates.$[two]":{ "productId" :NumberInt(1234), "rate" :500.0, "rateCardId":NumberInt(1), "month" :NumberInt(201802) }, "rates.$[three]":{ "productId" :NumberInt(1235), " rate" :700.0, "rateCardId":NumberInt(1), "month" :NumberInt(201802) } } }, "arrayFilters":[ { "one.productId":NumberInt(1234), "one.rateCardId":NumberInt (1), "one.month":NumberInt(201801) }, { "two.productId":NumberInt(1234), "two.rateCardId":NumberInt(1), "two.month":Nummer rInt(201802) }, { "three.productId":NumberInt(1235), "three.rateCardId":NumberInt(1), "three.month":NumberInt(201802) } ] }}, { "updateOne":{ "filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":{ "productId" :NumberInt(1234), "rateCardId":NumberInt(1), "month" :NumberInt(201801) } } } }, "update":{ "$push":{ "rates":{ "productId" :NumberInt(1234), "rate" :400.0, "rateCardId":NumberInt(1), " month" :NumberInt(201801) } } } }}, { "updateOne":{ "filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":{ "productId" :NumberInt(1234), "rateCardId":NumberInt(1), "month" :NumberInt(201802) } } } }, "update":{ "$push":{ "rates":{ "productId" :NumberInt(1234), "rate" :500.0, "rateCardId":NumberInt(1), "month" :NumberInt(201802) } } } }}, { "updateOne":{ "filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":{ "productId" :NumberInt(1235), " rateCardId":NumberInt(1), "month" :NumberInt(201802) } } } }, "update":{ "$push":{ "rates":{ "productId" :NumberInt(1235), "rate" :700.0, "rateCardId":NumberInt(1), "month" :NumberInt(201802) } } } }} ], { "ordered":true }) 

Bemærk $elemMatch inden for forespørgselsfilteret, da dette er et krav for at matche et array-element med "flere betingelser". Det havde vi ikke brug for på arrayFilters poster, fordi de kun se på hvert array-element, de allerede er anvendt på, men som en "forespørgsel" kræver betingelserne $elemMatch da simpel "punktnotation" ville returnere forkerte matches.

Se også $not operator bruges her til at "nægte" $elemMatch , da vores sande betingelser kun er at matche et dokument, der "ikke har matchende matrixelement" til de angivne betingelser, og det er det, der berettiger valg til at tilføje et nyt element.

Og den enkelte erklæring, der sendes til serveren, forsøger i det væsentlige fire opdateringshandlinger som én for at forsøge at opdatere matchede matrixelementer og en anden for hver af de tre "ændre sæt" forsøger at $push hvor dokumentet viste sig ikke at matche betingelserne for array-elementet i "ændringssættet".

Resultatet er derfor som forventet:

{ "_id" :12345, "_class" :"com.example.ProductRates", "rates" :[ {// matchede og opdaterede "productId" :1234, "rate" :400, "rateCardId " :1, "month" :201801 }, { // matchede og opdaterede "productId" :1234, "rate" :500, "rateCardId" :1, "month" :201802 }, { "productId" :1234, " rate" :400, "rateCardId" :2, "month" :201803 }, { "productId" :1235, "rate" :500, "rateCardId" :1, "month" :201801 }, { "productId" :1235, "rate" :234, "rateCardId" :2, "month" :201803 }, { // Dette blev tilføjet "productId" :1235, "rate" :700, "rateCardId" :1, "måned" :201802 } ]} 

Afhængigt af hvor mange elementer der faktisk ikke matchede bulkWrite() svar vil rapportere om, hvor mange af disse udsagn, der faktisk matchede og påvirkede et dokument. I dette tilfælde er det 2 matchet og modificeret, da den "første" opdateringsoperation matcher eksisterende array-indgange, og den "sidste" ændringsopdatering matcher, at dokumentet ikke indeholder array-indgangen og udfører $push at ændre.

Konklusion

Så der har du den kombinerede tilgang, hvor:

  • Den første del af "opdatering" i dit spørgsmål er meget let og kan gøres i en enkelt erklæring , som det er demonstreret i det første afsnit.

  • Den anden del, hvor der er et array-element, som "ikke findes i øjeblikket" inden for det aktuelle dokumentarray kræver dette faktisk, at du bruger bulkWrite() for at udstede "flere" operationer i en enkelt anmodning.

Derfor opdater , er "JA" til en enkelt handling. Men tilføje forskel betyder flere operationer. Men du kan kombinere de to tilgange, ligesom det er vist her.

Der er mange "fancy" måder, hvorpå du kan konstruere disse udsagn baseret på "change set"-arrayindholdet med kode, så du ikke behøver at "hardcode" hvert medlem.

Som et grundlæggende tilfælde for JavaScript og kompatibel med den nuværende udgivelse af mongo-skallen (som noget irriterende nok ikke understøtter objektspredningsoperatorer):

db.getCollection('avail_rates_copy').drop();db.getCollection('avail_rates_copy').insert( { "_id" :12345, "_class" :"com.example.ProductRates", " rates" :[ { "productId" :1234, "rate" :100, "rateCardId" :1, "month" :201801 }, { "productId" :1234, "rate" :200, "rateCardId" :1, " month" :201802 }, { "productId" :1234, "rate" :400, "rateCardId" :2, "month" :201803 }, { "productId" :1235, "rate" :500, "rateCardId" , "month" :201801 }, { "productId" :1235, "rate" :234, "rateCardId" :2, "month" :201803 } } });var changeSet =[ { "productId" :1234, "rate " :400.0, "rateCardId":1, "month" :201801 }, { "productId" :1234, "rate" :500.0, "rateCardId":1, "month" :201802 }, { "productId" , "r ate" :700.0, "rateCardId":1, "month" :201802 }];var arrayFilters =changeSet.map((obj,i) => Object.keys(obj).filter(k => k !='rate ' ) .reduce((o,k) => Object.assign(o, { [`u${i}.${k}`]:obj[k] }) ,{}));var $set =changeSet.reduce((o,r,i) => Object.assign(o, { [`rates.$[u${i}].rate`]:r.rate }), {});var updates =[ { "updateOne":{ "filter":{ "_id":12345 }, "update":{ $set }, arrayFilters }}, ...changeSet.map(obj => ( { "updateOne":{ " filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":Object.keys(obj).filter(k => k !='rate') .reduce((o) ,k) => Object.assign(o, { [k]:obj[k] }),{}) } } }, "update":{ "$push":{ "rates":obj } } }} ))];db.getCollection('avail_rates_copy').bulkWrite(opdateringer,{ ordnet:true }); 

Dette vil dynamisk konstruere en liste over "Bulk" opdateringsoperationer, som ville se sådan ud:

[ { "updateOne":{ "filter":{ "_id":12345 }, "update":{ "$set":{ "rates.$[u0].rate":400, " rates.$[u1].rate":500, "rates.$[u2].rate":700 } }, "arrayFilters":[ { "u0.productId":1234, "u0.rateCardId":1, " u0.month":201801 }, { "u1.productId":1234, "u1.rateCardId":1, "u1.month":201802 }, { "u2.productId":1235, "u2.rateCardId":1 , "u2.month":201802 } ] } }, { "updateOne":{ "filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":{ "productId" :1234, "rateCardId":1, "month":201801 } } } }, "update":{ "$push":{ "rates":{ "productId":1234, "rate":400, "rateCardId" :1 måned" :201801 } } } }, { "updateOne":{ "filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":{ "productId":1234, "rateCardId ":1, "month":201802 } } } }, "update":{ "$push":{ "rates":{ "productId":1234, "rate":500, "rateCardId":1, "month ":201802 } } } }, { "updateOne":{ "filter":{ "_id":12345, "rates":{ "$not":{ "$elemMatch":{ "productId":1235, " rateCardId":1, "month":201802 } } } }, "update":{ "$push":{ "rates":{ "productId":1235, "rate":700, "rateCardId":1, " måned":201802 } } } } }] 

Ligesom det blev beskrevet i den "lange form" af det generelle svar, men bruger selvfølgelig blot input-"array"-indholdet for at konstruere alle disse udsagn.

Du kan lave en sådan dynamisk objektkonstruktion på et hvilket som helst sprog, og alle MongoDB-drivere accepterer input af en eller anden type struktur, du har lov til at "manipulere", som derefter transformeres til BSON, før den rent faktisk sendes til serveren til udførelse.




  1. mongoimport Docker mislykkedes:fejl ved forbindelse til db-serveren:ingen tilgængelige servere

  2. Gem fil i Mongo's GridFS med ExpressJS efter upload

  3. PyMongo upsert kaster upsert skal være et tilfælde af bool fejl

  4. Hvordan bruger man uordnet bulk-indføring med Mongoskin?