在mongoDB中进行更新的聚合

时间:2013-10-15 15:14:20

标签: mongodb

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

输入:

{ 
    "_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,那么我必须将clear属性更新为"Yes"以获取具有相同cust_id的所有文档。

预期产出:

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

请建议我如何继续。

5 个答案:

答案 0 :(得分:14)

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

Psudocode:

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

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

答案 1 :(得分:6)

您需要分两步完成此操作:

  1. 识别总分大于200
  2. 的客户(cust_id
  3. 对于这些客户中的每一位,请将clear设置为Yes
  4. 第一部分你已经有了一个很好的解决方案。第二部分应该作为对数据库的单独update()调用来实现。

    Psudocode:

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

答案 2 :(得分:2)

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

此处提供更多信息:http://docs.mongodb.org/master/reference/operator/aggregation/out/

答案 3 :(得分:1)

现在,使用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
  }}}}
]);

答案 4 :(得分:0)

简短答案:为避免循环数据库查询,只需在末尾添加$out并指定您的集合,如下所示:

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

详细说明:当前的解决方案是遍历数据库查询,这在时间效率方面不是一个明智的选择,并且还会带来更多代码。 Mitar的答案不是通过聚合进行更新,而是使用Mongo更新内的聚合进行==相反。如果您想知道这样做的好处,那么您可以使用所有聚合管道,而不必局限于documentation中指定的几个聚合管道。

以下是汇总不适用于Mongo更新的示例:

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

这将输出带有新测试字段的更新的集合,如果“ someValueInArray”位于“ arrayFieldInFoo”中,则为true,否则为false。 Mongo.update当前可能不能,因为$ in不能在更新聚合中使用。

最后,请记住,shell命令和驱动程序(例如NodeJs Mongo驱动程序)的语法可能有所不同。