Najkrótsza odpowiedź brzmi zarówno „tak”, jak i „nie”.
Rzeczywiście istnieje sposób na dopasowanie poszczególnych elementów tablicy i aktualizowanie ich oddzielnymi wartościami w jednej instrukcji, ponieważ w rzeczywistości można zapewnić "wiele" arrayFilters
warunki i użyj tych identyfikatorów w oświadczeniu o aktualizacji.
Problem z tym konkretnym przykładem polega na tym, że jeden z wpisów w "zestawie zmian" (ostatni) nie pasuje do żadnego aktualnie obecnego elementu tablicy. „Przypuszczalnym” działaniem byłoby tutaj $push
ten nowy niedopasowany element do tablicy, w której nie został znaleziony. Jednak to konkretne działanie nie może być wykonane w "pojedynczej operacji" , ale możesz użyć bulkWrite()
wydawania "wielu" oświadczeń w tym przypadku.
Dopasowanie różnych warunków tablicy
Wyjaśniając to w punktach, rozważ dwie pierwsze pozycje w swoim „zestawie zmian”. Możesz zastosować „pojedynczy” instrukcja aktualizacji z wieloma arrayFilters
tak:
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)
}
]
}
)
Jeśli uruchomisz to, zobaczysz, że zmodyfikowany dokument staje się:
{
"_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
}
]
}
Zwróć uwagę, że określasz każdy „identyfikator” na liście arrayFilters
z wieloma warunkami, aby dopasować element, jak na przykład:
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
Tak więc każdy „warunek” skutecznie mapuje jako:
<identifier>.<property>
Więc wie, że musi patrzeć na "stawki"
tablica według instrukcji w bloku aktualizacji przez $[
:
"rates.$[one]"
I patrzy na każdy element "stawek"
aby dopasować się do warunków. Więc "jeden"
identyfikator pasowałby do warunków poprzedzonych przedrostkiem "jeden"
i podobnie dla drugiego zestawu warunków poprzedzonych przedrostkiem "dwa"
, dlatego rzeczywiste oświadczenie o aktualizacji dotyczy tylko tych, które spełniają warunki przypisane do identyfikatora.
Jeśli chcesz tylko "stawki"
w przeciwieństwie do całego obiektu, po prostu zanotuj jako:
{ "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }
Dodawanie niedopasowanych obiektów
Tak więc pierwsza część jest stosunkowo łatwa do zrozumienia, ale jak stwierdzono, wykonując $push
dla "elementu, którego nie ma" to inna sprawa, ponieważ w zasadzie potrzebujemy warunku zapytania na poziomie "dokumentu", aby określić, że element tablicy "brakuje".
Zasadniczo oznacza to, że musisz wydać aktualizację za pomocą $wciśnij
szukanie każdego elementu tablicy, aby sprawdzić, czy istnieje, czy nie. Jeśli go nie ma, dokument jest zgodny, a $wciśnij
jest wykonywany.
W tym miejscu bulkWrite()
wchodzi w grę i używasz go, dodając dodatkową aktualizację do naszej pierwszej operacji powyżej dla każdego elementu w "zestawie zmian":
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 }
)
Zwróć uwagę na $elemMatch
z filtrem zapytania, ponieważ jest to wymóg dopasowania elementu tablicy przez „wiele warunków”. Nie potrzebowaliśmy tego w arrayFilters
wpisy, ponieważ tylko spójrz na każdy element tablicy, do którego są już zastosowane, ale jako „zapytanie” warunki wymagają $elemMatch
ponieważ zwykła „notacja z kropkami” zwracałaby nieprawidłowe dopasowania.
Zobacz także $not
Operator jest tutaj używany do „negowania” $elemMatch
, ponieważ nasze prawdziwe warunki to dopasowanie tylko dokumentu, który „nie ma pasującego elementu tablicy” do podanych warunków i to uzasadnia wybór do dodania nowego elementu.
A ta pojedyncza instrukcja wydana serwerowi zasadniczo próbuje cztery aktualizacja operacji jako jedna przy próbie aktualizacji dopasowanych elementów tablicy, a druga dla każdego z trzech „Zestawy zmian” próbujące $push
gdzie stwierdzono, że dokument nie spełnia warunków elementu tablicy w „zestawie zmian”.
Wynik jest zatem zgodny z oczekiwaniami:
{
"_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
}
]
}
W zależności od tego, ile elementów faktycznie nie pasuje, bulkWrite()
odpowiedź poinformuje o tym, ile z tych oświadczeń faktycznie pasowało do dokumentu i dotyczyło go. W tym przypadku jest to 2
dopasowane i zmodyfikowane, ponieważ „pierwsza” operacja aktualizacji dopasowuje istniejące wpisy tablicy, a „ostatnia” aktualizacja zmiany odpowiada temu, że dokument nie zawiera wpisu tablicy i wykonuje $push
do modyfikacji.
Wniosek
Więc masz podejście połączone, gdzie:
-
Pierwsza część „aktualizacji” w Twoim pytaniu jest bardzo łatwa i można ją wykonać w jednym oświadczeniu , jak pokazano w pierwszej sekcji.
-
Druga część, w której znajduje się element tablicy, który "obecnie nie istnieje" w bieżącej tablicy dokumentów, wymaga to użycia
bulkWrite()
w celu wykonania "wielu" operacji w jednym żądaniu.
Dlatego zaktualizuj , jest „TAK” dla pojedynczej operacji. Ale dodawanie różnicy oznacza wiele operacji. Ale możesz połączyć te dwa podejścia, tak jak pokazano tutaj.
Istnieje wiele „wymyślnych” sposobów tworzenia tych instrukcji na podstawie zawartości tablicy „zestawu zmian” z kodem, więc nie trzeba „na sztywno” kodować każdego członka.
Jako podstawowy przypadek dla JavaScript i kompatybilny z aktualną wersją powłoki mongo (która nieco irytująco nie obsługuje operatorów rozprzestrzeniania obiektów):
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 });
Spowoduje to dynamiczne utworzenie listy operacji zbiorczej aktualizacji, która wyglądałaby następująco:
[
{
"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
}
}
}
}
}
]
Tak jak zostało to opisane w „długiej formie” ogólnej odpowiedzi, ale oczywiście po prostu używa wejściowej zawartości „tablicy” w celu skonstruowania wszystkich tych instrukcji.
Możesz wykonać taką dynamiczną konstrukcję obiektów w dowolnym języku, a wszystkie sterowniki MongoDB akceptują dane wejściowe pewnego typu struktury, którą możesz "manipulować", która jest następnie przekształcana do BSON, zanim zostanie faktycznie przesłana na serwer w celu wykonania.