【问题标题】:Aggregation with update in mongoDB在 mongoDB 中进行更新聚合
【发布时间】:2022-05-02 06:01:03
【问题描述】:

我有一个包含许多类似结构化文档的集合,其中两个文档看起来像

输入:

{ 
    "_id": ObjectId("525c22348771ebd7b179add8"), 
    "cust_id": "A1234", 
    "score": 500, 
    "status": "A"
    "clear": "No"
}

{ 
    "_id": ObjectId("525c22348771ebd7b179add9"), 
    "cust_id": "A1234", 
    "score": 1600, 
    "status": "B"
    "clear": "No"
}

默认情况下,所有文档的clear"No"

要求:我必须添加具有相同cust_id 的所有文档的分数,前提是它们属于status "A"status "B"。如果score 超过2000,那么我必须将所有具有相同cust_id 的文档的clear 属性更新为"Yes"

预期输出:

{ 
    "_id": ObjectId("525c22348771ebd7b179add8"), 
    "cust_id": "A1234", 
    "score": 500, 
    "status": "A"
    "clear": "Yes"
}

{
    "_id": ObjectId("525c22348771ebd7b179add9"), 
    "cust_id": "A1234", 
    "score": 1600, 
    "status": "B"
    "clear": "Yes"
}

是的,因为 1600+500 = 2100,并且 2100 > 2000。


我的方法: 我只能通过聚合函数得到总和,但更新失败

db.aggregation.aggregate([
    {$match: {
        $or: [
            {status: 'A'},
            {status: 'B'}
        ]
    }},
    {$group: {
        _id: '$cust_id',
        total: {$sum: '$score'}
    }},
    {$match: {
        total: {$gt: 2000}
    }}
])

请建议我如何进行。

【问题讨论】:

  • 您能描述一下失败是如何发生的吗?是否有错误或类似情况?
  • 本身没有错误,但我发现很难在一个语句中同时具有更新和聚合功能,我对 mongodb 很陌生,我正在尝试 cmd 中的场景。

标签: mongodb


【解决方案1】:

经历了很多麻烦,尝试了mongo shell,我终于找到了我的问题的解决方案。

伪代码:

# To get the list of customer whose score is greater than 2000
cust_to_clear=db.col.aggregate(
    {$match:{$or:[{status:'A'},{status:'B'}]}},
    {$group:{_id:'$cust_id',total:{$sum:'$score'}}},
    {$match:{total:{$gt:500}}})

# To loop through the result fetched from above code and update the clear
cust_to_clear.result.forEach
(
   function(x)
   { 
     db.col.update({cust_id:x._id},{$set:{clear:'Yes'}},{multi:true}); 
   }
)

如果您对同一问题有任何不同的解决方案,请发表评论。

【讨论】:

  • 所以你没有在一个声明中找到一种方法?例如,在单个 updateMany() 中?您必须编写一些 JavaScript 来循环聚合?
【解决方案2】:

在 Mongo 4.2 中,现在可以使用 update with aggregation pipeline 执行此操作。示例 2 包含如何进行条件更新的示例:

db.runCommand(
   {
      update: "students",
      updates: [
         {
           q: { },
           u: [
                 { $set: { average : { $avg: "$tests" } } },
                 { $set: { grade: { $switch: {
                                       branches: [
                                           { case: { $gte: [ "$average", 90 ] }, then: "A" },
                                           { case: { $gte: [ "$average", 80 ] }, then: "B" },
                                           { case: { $gte: [ "$average", 70 ] }, then: "C" },
                                           { case: { $gte: [ "$average", 60 ] }, then: "D" }
                                       ],
                                       default: "F"
                 } } } }
           ],
           multi: true
         }
      ],
      ordered: false,
      writeConcern: { w: "majority", wtimeout: 5000 }
   }
)

另一个例子:

db.c.update({}, [
  {$set:{a:{$cond:{
    if: {},    // some condition
      then:{} ,   // val1
      else: {}    // val2 or "$$REMOVE" to not set the field or "$a" to leave existing value
  }}}}
]);

【讨论】:

  • 根据文档。聚合管道中使用的 $set 和 $unset 分别是指聚合阶段 $set 和 $unset,而不是更新运算符 $set 和 $unset
  • 在您的示例中,我看不到聚合管道?,我期待这些行 db.collection.aggregate([{$match},{$lookup},{$match},{$update}。你要怎么做到这一点?或者这是无法实现的,您所说的是在u 子句中传递我的聚合管道,最后阶段为$set,它将更新选定的文档?
  • 这个解决方案是否有 mongodb.js 与 mongodb.js 等效?我似乎找不到它或任何与上面使用的 db.runCommand 等效的东西
【解决方案3】:

您需要分两步完成:

  1. 识别总分超过 200 分的客户 (cust_id)
  2. 对于这些客户中的每一个,将clear 设置为Yes

对于第一部分,您已经有了一个很好的解决方案。第二部分应实现为对数据库的单独update() 调用。

伪代码:

# Get list of customers using the aggregation framework
cust_to_clear = db.col.aggregate(
    {$match:{$or:[{status:'A'},{status:'B'}]}},
    {$group:{_id:'$cust_id', total:{$sum:'$score'}}},
    {$match:{total:{$gt:2000}}}
    )

# Loop over customers and update "clear" to "yes"
for customer in cust_to_clear:
    id = customer[_id]
    db.col.update(
        {"_id": id},
        {"$set": {"clear": "Yes"}}
    )

这并不理想,因为您必须为每个客户进行数据库调用。如果您需要经常执行此类操作,您可能会修改您的架构以包含每个文档中的总分。 (这必须由您的应用程序维护。)在这种情况下,您可以使用单个命令进行更新:

db.col.update(
    {"total_score": {"$gt": 2000}},
    {"$set": {"clear": "Yes"}},
    {"multi": true}
    )

【讨论】:

  • 嗨,感谢您的解决方案,我一直在尝试运行上面的代码,但它给了我一些错误。 1. 语法错误:意外标识符,2. 查询表达式中不能有未定义。您能否以格式编写完整的代码,因为我实际上是 mongoDB 的新手。
  • 我使用 psudocode 是因为它需要在应用程序级别实现,并且您无需指定正在使用的驱动程序。我可以做一个 Python (pymongo) 版本,或者想办法直接用 Mongo shell 的脚本来做。哪个更有帮助?
  • 我正在尝试通过 cmd 运行代码,我想直接用于 mongo shell 的脚本会有所帮助。我正在尝试通过尝试不同的场景来探索 mongo 的功能。您能否也请给我推荐一个在 mongo 上工作的好 UI,因为 cmd 非常令人沮丧。
【解决方案4】:

在 MongoDB 2.6. 中,可以使用相同的命令编写聚合查询的输出。

更多信息在这里:http://docs.mongodb.org/master/reference/operator/aggregation/out/

【讨论】:

    【解决方案5】:

    简短回答:为避免循环数据库查询,只需将$merge 添加到末尾并像这样指定您的集合:

    db.aggregation.aggregate([
        {$match: {
            $or: [
                {status: 'A'},
                {status: 'B'}
            ]
        }},
        {$group: {
            _id: '$cust_id',
            total: {$sum: '$score'}
        }},
        {$match: {
            total: {$gt: 2000}
        }},
        { $merge: "<collection name here>"}
    ])
    

    细化:当前的解决方案是循环数据库查询,这在时间效率方面并不好,而且代码也更多。 Mitar 的答案不是通过聚合进行更新,而是相反 => 在 Mongo 的更新中使用聚合。如果您想知道这样做有什么好处,那么您可以使用所有聚合管道,而不是仅限于documentation 中指定的少数几个。

    这是一个不适用于 Mongo 更新的聚合示例:

    db.getCollection('foo').aggregate([
      { $addFields: {
          testField: {
            $in: [ "someValueInArray", '$arrayFieldInFoo']
          } 
      }},
      { $merge : "foo" }]
    )
    

    这将输出带有新测试字段的更新集合,如果“someValueInArray”在“arrayFieldInFoo”中,则该字段为真,否则为假。这在 Mongo.update 中不可能实现,因为 $in 不能在更新聚合中使用。

    更新:从 $out 更改为 $merge,因为 $out 仅在更新整个集合时才有效,因为 $out 将整个集合替换为聚合的结果。 $merge 只有在聚合匹配文档时才会覆盖(更安全)。

    【讨论】:

      【解决方案6】:

      我找到的解决方案是使用 "$out"

      *) 例如添加一个字段:

      db.socios.aggregate(
          [
              {
                  $lookup: {
                      from: 'cuotas',
                      localField: 'num_socio',
                      foreignField: 'num_socio',
                      as: 'cuotas'
                  }
              },
              { 
                  $addFields: { codigo_interno: 1001 } 
              },
              {
                  $out: 'socios' //Collection to modify
              }
          ]
      )
      

      *) 例如修改字段:

      db.socios.aggregate(
              [
                  {
                      $lookup: {
                          from: 'cuotas',
                          localField: 'num_socio',
                          foreignField: 'num_socio',
                          as: 'cuotas'
                      }
                  },
                  { 
                      $set: { codigo_interno: 1001 } 
                  },
                  {
                      $out: 'socios' //Collection to modify
                  }
              ]
          )
      

      【讨论】:

        猜你喜欢
        • 2015-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-04
        • 2019-05-13
        • 1970-01-01
        • 2018-06-14
        相关资源
        最近更新 更多