MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Massenaktualisierung des Arrays von übereinstimmenden Unterdokumenten in Mongodb

Die kürzeste Antwort lautet sowohl „ja“ als auch „nein“.

Es gibt tatsächlich eine Möglichkeit, einzelne Array-Elemente abzugleichen und sie mit separaten Werten in einer einzigen Anweisung zu aktualisieren, da Sie tatsächlich "mehrere" arrayFilters bereitstellen können Bedingungen und verwenden Sie diese Kennungen in Ihrer Aktualisierungserklärung.

Das Problem mit Ihrem speziellen Beispiel hier ist, dass einer der Einträge in Ihrem "Änderungssatz" (der letzte) mit keinem derzeit vorhandenen Array-Mitglied übereinstimmt. Die "angenommene" Aktion hier wäre $push dieses neue nicht übereinstimmende Mitglied in das Array, wo es nicht gefunden wurde. Diese bestimmte Aktion kann es jedoch nicht in einem "einzelnen Vorgang" erfolgen , aber Sie können bulkWrite() "mehrere" Erklärungen abzugeben, um diesen Fall abzudecken.

Abgleich verschiedener Array-Bedingungen

Erklären Sie das in Punkten, betrachten Sie die ersten beiden Elemente in Ihrem "Änderungssatz". Sie können einen "einzelnen" Antrag stellen update-Anweisung mit mehreren arrayFilters so:

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

Wenn Sie das ausgeführt haben, sehen Sie, dass das geänderte Dokument zu:

wird
{
        "_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
                }
        ]
}

Beachten Sie hier, dass Sie jeden "identfier" in der Liste der arrayFilters angeben mit mehreren Bedingungen, um das Element wie folgt abzugleichen:

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

Jede "Bedingung" wird also effektiv abgebildet als:

  <identifier>.<property>

Es muss sich also die "Preise" ansehen Array durch die Anweisung im Aktualisierungsblock durch den $[] :

 "rates.$[one]"

Und schaut sich jedes Element von "Rates" an den Bedingungen entsprechen. Also die "eins" der Bezeichner würde den Bedingungen entsprechen, denen "one" vorangestellt ist und ebenso für den anderen Satz von Bedingungen mit dem Präfix "two" , daher gilt die eigentliche Aktualisierungsanweisung nur für diejenigen, die den dem Bezeichner zugewiesenen Bedingungen entsprechen.

Wenn Sie nur die "Preise" wollten Eigenschaft im Gegensatz zum ganzen Objekt, notieren Sie einfach als:

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

Hinzufügen von nicht übereinstimmenden Objekten

Der erste Teil ist also relativ einfach zu verstehen, aber wie gesagt, mache einen $push für das "nicht vorhandene Element" ist das anders, da wir grundsätzlich eine Abfragebedingung auf der "Dokument"-Ebene benötigen, um festzustellen, dass ein Array-Element "fehlt".

Das bedeutet im Wesentlichen, dass Sie ein Update mit dem Code $push Suche nach jedem Array-Element, um zu sehen, ob es existiert oder nicht. Wenn es nicht vorhanden ist, dann ist das Dokument eine Übereinstimmung und der $push durchgeführt wird.

Hier ist bulkWrite() ins Spiel kommt, und Sie verwenden es, indem Sie unserer ersten Operation oben für jedes Element im "Änderungssatz" ein zusätzliches Update hinzufügen:

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

Beachten Sie den $elemMatch innerhalb des Abfragefilters, da dies eine Voraussetzung ist, um ein Array-Element durch "mehrere Bedingungen" abzugleichen. Wir brauchten das bei den arrayFilters nicht Einträge, weil sie nur Sehen Sie sich jedes Array-Element an, auf das sie bereits angewendet werden, aber als "Abfrage" erfordern die Bedingungen $elemMatch da eine einfache "Punktnotation" falsche Übereinstimmungen zurückgeben würde.

Siehe auch $not Der Operator wird hier verwendet, um den $elemMatch , da unsere wahren Bedingungen nur mit einem Dokument übereinstimmen sollen, das "kein übereinstimmendes Array-Element hat" den bereitgestellten Bedingungen, und das rechtfertigt die Auswahl zum Anhängen eines neuen Elements.

Und diese einzelne Anweisung, die an den Server ausgegeben wird, versucht im Wesentlichen vier Update-Vorgänge als einen für den Versuch, übereinstimmende Array-Elemente zu aktualisieren, und einen weiteren für jeden der drei "Change Sets" versucht $push wobei festgestellt wurde, dass das Dokument nicht den Bedingungen für das Array-Element im "Änderungssatz" entspricht.

Das Ergebnis ist daher wie erwartet:

{
        "_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
                }
        ]
}

Abhängig davon, wie viele Elemente tatsächlich nicht mit bulkWrite() übereinstimmen response wird berichten, wie viele dieser Aussagen tatsächlich mit einem Dokument übereinstimmten und sich darauf auswirkten. In diesem Fall ist es 2 abgeglichen und geändert, da die "erste" Aktualisierungsoperation mit bestehenden Array-Einträgen übereinstimmt und die "letzte" Änderungsaktualisierung übereinstimmt, dass das Dokument den Array-Eintrag nicht enthält und die $push zu ändern.

Schlussfolgerung

Da haben Sie also den kombinierten Ansatz, wobei:

  • Der erste Teil des "Aktualisierens" in Ihrer Frage ist sehr einfach und kann in einer einzelnen Anweisung erledigt werden , wie im ersten Abschnitt gezeigt wird.

  • Der zweite Teil, wo es ein Array-Element gibt, das "derzeit nicht existiert" Innerhalb des aktuellen Dokumentarrays erfordert dies tatsächlich die Verwendung von bulkWrite() um "mehrere" Operationen in einer einzigen Anfrage auszugeben.

Deshalb aktualisieren , ist "JA" zu einer einzelnen Operation. Aber Hinzufügen von Unterschieden bedeutet mehrere Operationen. Sie können die beiden Ansätze jedoch wie hier demonstriert kombinieren.

Es gibt viele "ausgefallene" Möglichkeiten, wie Sie diese Anweisungen basierend auf dem Inhalt des "Änderungssatzes" des Arrays mit Code erstellen können, sodass Sie nicht jedes Element "fest codieren" müssen.

Als grundlegender Fall für JavaScript und kompatibel mit der aktuellen Version der Mongo-Shell (die etwas ärgerlicherweise keine Objektverbreitungsoperatoren unterstützt):

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 });

Dadurch wird dynamisch eine Liste von "Massen"-Aktualisierungsvorgängen erstellt, die wie folgt aussehen würde:

[
  {
    "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
          }
        }
      }
    }
  }
]

Genau wie in der "Langform" der allgemeinen Antwort beschrieben, verwendet aber natürlich einfach den Inhalt des Eingabe-"Arrays", um all diese Anweisungen zu erstellen.

Sie können eine solche dynamische Objektkonstruktion in jeder Sprache durchführen, und alle MongoDB-Treiber akzeptieren die Eingabe einer Art von Struktur, die Sie „manipulieren“ dürfen, die dann in BSON umgewandelt wird, bevor sie tatsächlich zur Ausführung an den Server gesendet wird.