gpt4 book ai didi

mongodb - Mongodb中匹配子文档的批量更新数组

转载 作者:可可西里 更新时间:2023-11-01 10:46:48 29 4
gpt4 key购买 nike

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

对关联的RateCard的任何更改都将更新传播到“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.[].productIdrates.[].monthrates.[].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应用“单个”update语句,如下所示:

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列表中使用多个条件指定每个“identifier”以匹配元素,如下所示:
  {
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},

因此,每个“条件”有效地映射为:
  <identifier>.<property>

所以它知道要通过update块中的语句查看 "rates"数组:
 "rates.$[one]"

并查看 $[<indentifier>]的每个元素以匹配条件。因此, "rates"标识符将匹配以 "one"为前缀的条件,同样,对于以 "one"为前缀的另一组条件,因此实际的update语句仅适用于与分配给该标识符的条件匹配的那些条件。
如果您只是想要 "two"属性而不是整个对象,那么您只需将其标记为:
{ "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }

添加不匹配的对象
因此,第一部分比较容易理解,但正如所述,对“element which is not there”执行a "rates"是另一回事,因为我们基本上需要“document”级别的查询条件来确定数组元素是否“missing”。
这实际上意味着,您需要发出一个更新,其中 $push查找每个数组元素,以查看它是否存在。如果不存在,则文档是匹配的,并执行 $push
这就是 $push起作用的地方,您可以通过在上面的第一个操作中为“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 }
)

注意不使用查询过滤器的 bulkWrite(),因为这是通过“多个条件”匹配数组元素的要求。我们不需要在 $elemMatch条目上这样做,因为它们只查看它们已经应用到的每个数组项,但是作为一个“查询”,条件需要 arrayFilters因为简单的“点表示法”将返回不正确的匹配。
另请参见这里使用的 $elemMatch运算符“否定” $not,因为我们的真正条件是只将“没有匹配数组元素”的文档匹配到所提供的条件,这就是为什么选择追加新元素的理由。
向服务器发出的单个语句实际上尝试四个更新操作,一个用于尝试更新匹配的数组元素,另一个用于尝试 $elemMatch的三个“更改集”中的每一个,其中发现文档与“更改集”。
因此,结果与预期一样:
{
"_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
}
]
}

根据实际与 $push不匹配的元素的数量,响应将报告这些语句中实际与文档匹配并影响文档的元素数量。在本例中,它是 bulkWrite()匹配和修改的,因为“first”更新操作匹配现有数组项,“last”更改更新匹配文档不包含数组项并执行 2来修改。
结论
这里有一个组合方法,其中:
问题中“更新”的第一部分非常简单,可以用一个语句完成,如第一部分所示。
第二部分,如果当前文档数组中存在一个“当前不存在”的数组元素,则实际上需要使用 $push才能在单个请求中发出“多个”操作。
因此更新,是对单个操作的“是”。但增加差异意味着要进行多种操作。但是你可以把这两种方法结合起来,正如这里所展示的。
有很多“奇特”的方法可以基于“change set”数组内容用代码构造这些语句,因此不需要对每个成员进行“硬编码”。
作为javascript的一个基本案例,它与mongo shell的当前版本兼容(mongoshell不支持object spread操作符,这让人有些恼火):
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。
注意: bulkWrite()<identifier>必须由字母数字字符组成,并且必须以字母字符开头。因此,在构造动态语句时,我们在前面加上 arrayFilters前缀,然后是正在处理的每个项的当前数组索引。

关于mongodb - Mongodb中匹配子文档的批量更新数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49867541/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com