MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Zbiorcza aktualizacja tablicy pasujących dokumentów podrzędnych w Mongodb

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.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Korzystanie z wartości dynamicznej w agregacji

  2. Najlepsze praktyki aktualizacji asynchronicznie zduplikowanych danych w mongodb

  3. Nie mogę zmusić walidatora mangusty do pracy

  4. Jakich plików lub katalogów oczekuje mongorestore przy użyciu flagi -d?

  5. Wygaśnięcie TTL dokumentu referencyjnego Mongoose