当前位置: 首页 > 知识库问答 >
问题:

MongoDB中匹配子文档的批量更新数组

羊舌青青
2023-03-14

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

对相关联的计费卡的任何更改,都会将更新传播到“费率”数组中的多个子文档。

以下是需要应用于上述文档的更改

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

有没有一种方法可以在数组速率下更新子文档,而无需将整个文档加载到内存中,以便合并更改?假设我的子文档标识符是费率的组合。[]. productId费率。[]。月费率。[]。rateCardId

我可以使用一次更新多个文档$[

db.avail.rates_copy.update(
  { "_id" : 12345 },
  { $set: { "rates.$[item].rate": 0  } },
  { multi: true, 
   arrayFilters: [ { "item.rateCardId": {$in: [ 1, 2]} } ]
  }
)

而在我的例子中,值将根据上述标识符组合在文档之间更改,这些标识符组合来自不同的系统。

有没有办法说,用新值更新变更集中与(productId、month和rateCardId)匹配的所有子文档。


共有1个答案

凌宏大
2023-03-14

在最简短的回答中,它既是“是”又是“否”。

确实有一种方法可以匹配单个数组元素,并在单个语句中使用单独的值更新它们,因为您实际上可以提供“多个”数组过滤器条件,并在更新语句中使用这些标识符。

这里特定示例的问题是,“更改集”(最后一个)中的一个条目实际上与当前存在的任何数组成员都不匹配。此处的“假定”操作是将新的未匹配成员推送到未找到它的数组中。但是,该特定操作不能在“单个操作”中完成,但您可以使用bulkWrite()发出“多个”语句来涵盖该情况。

解释这一点,在你的“改变集”中考虑前两个项目。您可以使用多个arrayFilter应用“单个”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
                }
        ]
}

请注意,在阵列过滤器列表中指定每个“标识符”,并使用多个条件匹配元素,如下所示:

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

因此,每个“条件”有效地映射为:

  <identifier>.<property>

所以它知道要通过$[的更新块中的语句来查看“速率”数组

 "rates.$[one]"

并查看“rates”的每个元素以匹配条件。因此,“one”标识符将匹配前缀为“one”的条件,同样,对于前缀为“two”的另一组条件,因此实际的update语句仅适用于与分配给标识符的条件匹配的条件。

如果您只是想要"比率"属性而不是整个对象,那么您只需注释为:

{ "$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,因为这是通过“多个条件”匹配数组元素的要求。我们不需要在ArrayFilter条目上使用它,因为它们只查看已应用到的每个数组项,但作为“查询”,条件要求$elemMatch,因为简单的“点符号”会返回不正确的匹配。

另请参见此处使用的$not运算符“否定”$elemMatch,因为我们的真正条件是仅将“没有匹配数组元素”的文档与提供的条件匹配,这就是选择附加新元素的理由。

向服务器发出的这条语句实际上尝试了四个更新操作,一个用于尝试更新匹配的数组元素,另一个用于尝试$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进行修改。

这就是组合方法,其中:

>

  • 你问题中更新的第一部分非常简单,可以在一个语句中完成,正如第一部分所展示的。

    第二部分,在当前文档数组中有一个“目前不存在”的数组元素,这实际上需要您使用bulk↑()以便在一个请求中发出“多个”操作。

    因此,对于单个操作,更新为“是”。但增加差异意味着多重操作。但是您可以将这两种方法结合起来,正如本文所示。

    有许多“花哨”的方式可以用代码基于“更改集”数组内容构建这些语句,因此您不需要对每个成员进行“硬编码”。

    作为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。

    注:

  •  类似资料:
    • 在我的MongoDB中,我有文档,如下所示。如何更新内部文档结构中的文档:<code>〔{1},〔{2}〕,〔{3}〕、〔{4}〕</code>。 例如:子子文档中的子文档具有和以及 和

    • 问题内容: 我正在使用MongoDB数据库,该数据库的收集模型包括 班级 , 学生 , 学科 和[学术] 表现 。以下是基于猫鼬的架构和模型: 该集合的文件是最复杂的地段; 示例文档为: 我能够使用以下代码检索现有的班级文档并将其添加到分数中: 但是,如何添加/更新/删除该特定学生的成绩?我需要能够通过以下方式与集合进行交互: 检索所有学生或特定学生的分数(检索数组中的特定元素) 为特定主题添加/

    • 我目前正在Java应用程序上使用MongoDB的驱动程序。假设我将以下两个文档存储在一个集合中: 文件A: 文件B: 现在,我想通过使用key1和key2的值从集合中检索DocumentB的精确匹配,而不返回documenta,而不是_id,因为我事先不知道它。 如果我只是使用DocumentB作为查询(没有_id),Mongo也将返回DocumentA,因为它匹配所有的键和值,不考虑Docume

    • 主要内容:update() 方法,save() 方法在 MongoDB 中,可以使用 update() 和 save() 方法来更新集合中的文档。其中 update() 方法可以更新现有文档中的值,而 save() 方法则可以使用传入文档来替换已有文档。 update() 方法 update() 方法用于更新现有文档中的值,其语法格式如下: db.collection_name.update(     <query>,     <update>,

    • 问题内容: 我正在使用MongoDB 3.2和MongoDB Java驱动程序3.2。我有一个包含数百个更新文档的数组,这些文档现在应该保存/存储在MongoDB中。为了做到这一点,我遍历数组,并为该数组中的每个文档调用方法。 现在,我想通过批量更新重新实现此逻辑。我尝试在使用MongoDB Java驱动程序3.2的MongoDB 3.2中找到批量更新的示例。 我尝试了这段代码: 但是,似乎这种方

    • 将mongodb与pymongo一起使用,我有以下文档: 我想更新示例子文档(这是一个数组元素,因为可能有多个示例)。我有以下代码,但它不工作... 谁能告诉我这个有什么问题吗?