问题描述
我在Mongodb 3.6上运行.以下是我的文档结构,其中存储了产品列表的月费率信息:
{
"_id": 12345,
"_class": "com.example.ProductRates",
"rates": [
{
"productId": NumberInt(1234),
"rate": 100.0,
"rateCardId": NumberInt(1),
"month": NumberInt(201801)
},
{
"productId": NumberInt(1234),
"rate": 200.0,
"rateCardId": NumberInt(1),
"month": NumberInt(201802)
},
{
"productId": NumberInt(1234),
"rate": 400.0,
"rateCardId": NumberInt(2),
"month": NumberInt(201803)
},
{
"productId": NumberInt(1235),
"rate": 500.0,
"rateCardId": NumberInt(1),
"month": NumberInt(201801)
},
{
"productId": NumberInt(1235),
"rate": 234,
"rateCardId": NumberInt(2),
"month": NumberInt(201803)
}
]
}
对价目表的任何更改都会将更新传播到'rates'数组中的多个子文档.
以下是需要在上述文档中应用的更改
{
"productId" : NumberInt(1234),
"rate" : 400.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201801)
},
{
"productId" : NumberInt(1234),
"rate" : 500.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
},
{
"productId" : NumberInt(1235),
"rate" : 700.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
是否有一种方法可以递增地更新数组"rates"下的子文档,而无需将整个文档加载到内存中以合并更改?可以说我的子文档标识符是rates.[].productId
,rates.[].month
和rates.[].rateCardId
的组合.
我能够使用3.6中的$[<identifier>]
一次更新多个文档,但是具有相同的值.
db.avail.rates_copy.update(
{ "_id" : 12345 },
{ $set: { "rates.$[item].rate": 0 } },
{ multi: true,
arrayFilters: [ { "item.rateCardId": {$in: [ 1, 2]} } ]
}
)
在我看来,基于上述标识符组合(来自不同系统),文档之间的值将发生变化.
有没有办法这样说,用新值更新变更集中与(productId,month和rateCardId)匹配的所有子文档.
最简短的答案是是"和否".
确实存在一种匹配单个数组元素并在单个语句中用单独的值更新它们的方法,因为实际上您可以提供多个" arrayFilters
条件,并在您的update语句中使用这些标识符.
这里特定样本的问题在于,更改集"中的一项(最后一项)实际上与当前存在的任何数组成员都不匹配.这里的假定"操作将是 $push
找不到数组的新的不匹配成员.但是,该特定操作 不能通过单个操作" 完成,但是您可以使用 bulkWrite()
发出多个"语句来解决这种情况.
匹配不同的数组条件
以点数解释,请考虑变更集"中的前两项.您可以使用带有多个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
}
]
}
请注意,您要在arrayFilters
列表中指定具有多个条件的每个标识符",以匹配元素,如下所示:
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
因此每个条件"有效地映射为:
<identifier>.<property>
因此,它知道要通过 $[<indentifier>]
:
"rates.$[one]"
并查看"rates"
的每个元素以匹配条件.因此,"one"
标识符将匹配以"one"
前缀的条件,并且对于其他以"two"
前缀的条件集也是如此,因此,实际的更新语句仅适用于与分配给该标识符的条件相匹配的条件.
如果您只想使用"rates"
属性而不是整个对象,那么您只需将其标记为:
{ "$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
,这是我们的真实条件只能将与数组元素不匹配" 的文档与提供的条件进行匹配,这就是选择附加新元素的理由.
发出给服务器的那条语句实际上尝试进行 4 个更新操作,作为一种尝试更新匹配的数组元素的操作,而对 3 个变更集的每个操作执行另一种操作尝试$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()
,以便在单个请求中发出多个"操作.
因此, update 对单个操作为是".但是添加差异表示多项操作.但是您可以将两种方法结合起来,如此处所示.
您可以通过多种奇特"方式根据带有代码的变更集"数组内容来构造这些语句,因此您无需硬编码"每个成员.
作为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,然后再将其实际发送到服务器以供执行.
I am running on Mongodb 3.6. Below is the structure of my document, which stores monthly rate information for list of products:
{
"_id": 12345,
"_class": "com.example.ProductRates",
"rates": [
{
"productId": NumberInt(1234),
"rate": 100.0,
"rateCardId": NumberInt(1),
"month": NumberInt(201801)
},
{
"productId": NumberInt(1234),
"rate": 200.0,
"rateCardId": NumberInt(1),
"month": NumberInt(201802)
},
{
"productId": NumberInt(1234),
"rate": 400.0,
"rateCardId": NumberInt(2),
"month": NumberInt(201803)
},
{
"productId": NumberInt(1235),
"rate": 500.0,
"rateCardId": NumberInt(1),
"month": NumberInt(201801)
},
{
"productId": NumberInt(1235),
"rate": 234,
"rateCardId": NumberInt(2),
"month": NumberInt(201803)
}
]
}
Any changes to the ratecard associated, will propagate updates to multiple sub documents in the 'rates' array.
Below are the changes that needs to be applied on the above document
{
"productId" : NumberInt(1234),
"rate" : 400.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201801)
},
{
"productId" : NumberInt(1234),
"rate" : 500.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
},
{
"productId" : NumberInt(1235),
"rate" : 700.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
Is there a way to update the subdocuments under the array 'rates', incrementally without loading the entire document into the memory, inorder to merge the changes? Lets say my identifiers for the sub documents are combination of rates.[].productId
, rates.[].month
and rates.[].rateCardId
.
I am able to update multiple documents at once using $[<identifier>]
in 3.6, but with same value.
db.avail.rates_copy.update(
{ "_id" : 12345 },
{ $set: { "rates.$[item].rate": 0 } },
{ multi: true,
arrayFilters: [ { "item.rateCardId": {$in: [ 1, 2]} } ]
}
)
Whereas in my case, values will change between the documents based on the above mentioned identifier combinations, which comes from a different system.
Is there a way to say that, update all the sub-documents that matches with( productId, month and rateCardId) from the changeset, with new values.
In the shortest answer, it's both "yes" and "no".
There is indeed a way to match individual array elements and update them with separate values in a single statement, since you can in fact provide "multiple" arrayFilters
conditions and use those identifiers in your update statement.
The problem with your particular sample here is that one of the entries in your "change set" ( the last one ) does not actually match any array member that is currently present. The "presumed" action here would be to $push
that new un-matched member into the array where it was not found. However that particular action cannot be done in a "single operation", but you can use bulkWrite()
to issue "multiple" statements to cover that case.
Matching Different Array Conditions
Explaining that in points, consider the first two items in your "change set". You can apply a "single" update statement with multiple arrayFilters
like this:
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)
}
]
}
)
If you ran that you would see the modified document becomes:
{
"_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
}
]
}
Note here that you specify each "identfier" within the list of arrayFilters
with multiple conditions to match the element like so:
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
So each "condition" effectively maps as:
<identifier>.<property>
So it knows to be looking at the "rates"
array by the statement in the update block by the $[<indentifier>]
:
"rates.$[one]"
And looks at each element of "rates"
to match the conditions. So the "one"
identifier would match the conditions prefixed with "one"
and likewise for the other set of conditions prefixed with "two"
, therefore the actual update statement applies only to those which matches the conditions assigned to the identifier.
If you just wanted the "rates"
property as opposed to the whole object, then you just notate as:
{ "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }
Adding Un-matched Objects
So the first part is relatively simple to comprehend, but as stated doing a $push
for the "element which is not there" is a different matter, since we basically need a query condition on the "document" level in order to determine that an array element is "missing".
What this essentially means is that you need to issue an update with the $push
looking for each array element to see if it exists or not. When it is not present, then the document is a match and the $push
is performed.
This is where bulkWrite()
comes into play, and you use it by adding an additional update to our first operation above for every element in the "change set":
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 }
)
Note the $elemMatch
withing the query filter, as this is a requirement to match an array element by "multiple conditions". We didn't need that on the arrayFilters
entries because they only look at each array item they are applied to already, but as a "query" the conditions require $elemMatch
as simple "dot notation" would return incorrect matches.
Also see the $not
operator is used here to "negate" the $elemMatch
, as our true conditions are to only match a document which "has not matching array element" to the provided conditions, and that is what justifies selection for appending a new element.
And that single statement issued to the server essentially attempts four update operations as one for attempting to update matched array elements, and another for each of the three "change sets" attempting to $push
where the document was found to not match the conditions for the array element in the "change set".
The result is therefore as expected:
{
"_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
}
]
}
Depending on how many elements where actually un-matched the bulkWrite()
response will report on how many of those statement actually matched and affected a document. In this case it's 2
matched and modified, since the "first" update operation matches existing array entries, and the "last" change update matches that the document does not contain the array entry and performs the $push
to modify.
Conclusion
So there you have the combined approach, where:
The first part of "updating" in your question is very easy and can be done in a single statement, as is demonstrated in the first section.
The second part where there is an array element which "does not presently exist" within the current document array, this actually requires you use
bulkWrite()
in order to issue "multiple" operations in a single request.
Therefore update, is "YES" to a single operation. But adding difference means multiple operations. But you can combine the two approaches just as is demonstrated here.
There are many "fancy" ways in which you can construct these statements based on the "change set" array contents with code, so you don't need to "hardcode" each member.
As a basic case for JavaScript and compatible with the current release of the mongo shell ( which somewhat annoyingly does not support object spread operators ):
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 });
This will dynamically construct a list of "Bulk" update operations which would look like:
[
{
"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
}
}
}
}
}
]
Just like was described in the "long form" of the general answer, but of course simply uses the input "array" content in order to construct all of those statements.
You can do such dynamic object construction in any language, and all MongoDB drivers accept input of some type of structure you are allowed to "manipulate" which is then transformed to BSON before it's actually sent to the server for execution.
这篇关于Mongodb中匹配子文档的批量更新数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!