Най-краткият отговор е едновременно „да“ и „не“.
Наистина има начин да съпоставите отделни елементи на масива и да ги актуализирате с отделни стойности в един израз, тъй като всъщност можете да предоставите „множество“ arrayFilters
условия и използвайте тези идентификатори в изявлението си за актуализиране.
Проблемът с вашата конкретна извадка тук е, че един от записите във вашия "набор за промяна" (последният) всъщност не съответства на нито един член на масива, който присъства в момента. „Предполагаемото“ действие тук би било $pushкод>
този нов несъвпадащ член в масива, където не е намерен. Това конкретно действие обаче не може да се извърши в "една операция" , но можете да използвате bulkWrite()
за издаване на "множество" извлечения за покриване на този случай.
Съвпадение на различни условия на масив
Обяснявайки това в точки, помислете за първите два елемента във вашия „набор за промяна“. Можете да приложите "single" израз за актуализиране с множество arrayFilters
като това:
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)
}
]
}
)
Ако изпълните това, ще видите, че модифицираният документ става:
{
"_id" : 12345,
"_class" : "com.example.ProductRates",
"rates" : [
{ // Matched and changed this by one
"productId" : 1234,
"rate" : 400,
"rateCardId" : 1,
"month" : 201801
},
{ // And this as two
"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
}
]
}
Тук имайте предвид, че посочвате всеки „identfier“ в списъка с arrayFilters
с множество условия за съвпадение на елемента по следния начин:
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
Така че всяко „условие“ ефективно се картографира като:
<identifier>.<property>
Така че знае, че трябва да гледа "тарифите"
масив от израза в блока за актуализиране от $[
:
"rates.$[one]"
И разглежда всеки елемент от "rates"
да отговаря на условията. Така че "едно"
идентификатор ще съответства на условията с префикс "one"
и по същия начин за другия набор от условия с префикс "две"
, следователно действителното изявление за актуализиране се прилага само за тези, които отговарят на условията, присвоени на идентификатора.
Ако просто искате "тарифите"
свойство за разлика от целия обект, тогава просто отбелязвате като:
{ "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }
Добавяне на несъвпадащи обекти
Така че първата част е относително лесна за разбиране, но както беше посочено, правете $push
за „елементът, който не е там“ е различен въпрос, тъй като основно се нуждаем от условие за заявка на ниво „документ“, за да определим, че елемент от масива „липсва“.
Това по същество означава, че трябва да издадете актуализация с $push
търси всеки елемент от масива, за да види дали съществува или не. Когато не присъства, тогава документът е съвпадение и $push
се изпълнява.
Това е мястото, където bulkWrite()
влиза в действие и вие го използвате, като добавите допълнителна актуализация към първата ни операция по-горе за всеки елемент в „набора за промяна“:
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": NumberInt(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 }
)
Обърнете внимание на $elemMatch
в рамките на филтъра на заявката, тъй като това е изискване за съвпадение на елемент от масив чрез „множество условия“. Нямахме нужда от това на arrayFilters
записи, тъй като те самос разгледайте всеки елемент от масива, към който вече са приложени, но като "заявка" условията изискват $elemMatch
тъй като простата "точкова нотация" би върнала неправилни съвпадения.
Вижте също $not
операторът се използва тук за „отхвърляне“ на $elemMatchкод>
, тъй като истинските ни условия са да отговаряме само на документ, който "няма съответстващ елемент на масив" към предоставените условия и това е, което оправдава избора за добавяне на нов елемент.
И това единствено изявление, издадено на сървъра, по същество прави четири опита операции за актуализиране като една за опит за актуализиране на съответстващи елементи на масив и друга за всеки от трите "набори за промяна", опитвайки се да $push
където е установено, че документът не отговаря на условията за елемента на масива в „набора за промяна“.
Следователно резултатът е както се очаква:
{
"_id" : 12345,
"_class" : "com.example.ProductRates",
"rates" : [
{ // matched and updated
"productId" : 1234,
"rate" : 400,
"rateCardId" : 1,
"month" : 201801
},
{ // matched and updated
"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
},
{ // This was appended
"productId" : 1235,
"rate" : 700,
"rateCardId" : 1,
"month" : 201802
}
]
}
В зависимост от това колко елемента действително не отговарят на bulkWrite()
отговорът ще докладва колко от тези твърдения действително съвпадат и засягат документ. В този случай това е 2
съответстващи и модифицирани, тъй като „първата“ операция за актуализиране съответства на съществуващи записи в масив, а „последната“ актуализация на промяната съответства на това, че документът не съдържа записа в масива и изпълнява $push
за промяна.
Заключение
Така че имате комбинирания подход, където:
-
Първата част от „актуализацията“ във вашия въпрос е много лесна и може да бъде направена с единствен израз , както е показано в първия раздел.
-
Втората част, където има елемент от масив, който "в момента не съществува" в рамките на текущия документен масив, това всъщност изисква да използвате
bulkWrite()
за да издадете "множество" операции в една заявка.
Затова актуализирайте , е „ДА“ на една операция. Но добавяне на разлика означава множество операции. Но можете да комбинирате двата подхода точно както е показано тук.
Има много "фантастични" начини, по които можете да конструирате тези изрази въз основа на съдържанието на масива "промяна на набор" с код, така че не е необходимо да "твърдо кодирате" всеки член.
Като основен случай за JavaScript и съвместим с текущата версия на mongo shell (който донякъде досадно не поддържа оператори за разпространение на обекти):
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" : 1,
"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" : 1235,
"rate" : 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(updates,{ ordered: true });
Това динамично ще изгради списък с операции за „Групова“ актуализация, който ще изглежда така:
[
{
"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,
"month": 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,
"month": 201802
}
}
}
}
}
]
Точно както беше описано в „дългата форма“ на общия отговор, но разбира се просто използва съдържанието на входния „масив“, за да конструира всички тези твърдения.
Можете да правите такова динамично конструиране на обекти на всеки език и всички драйвери на MongoDB приемат въвеждане на някакъв тип структура, която ви е позволено да „манипулирате“, която след това се трансформира в BSON, преди действително да бъде изпратена до сървъра за изпълнение.