Агрегация с обновлением в 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}
    }}
])

Пожалуйста, предложите мне, как мне поступить.

Ответ 1

После многих неприятностей, экспериментируя с оболочкой монго, я наконец получил решение моего вопроса.

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

Прошу прокомментировать, если у вас есть другое решение по тому же вопросу.

Ответ 2

Вам нужно сделать это в два этапа:

  1. Определите клиентов (cust_id) с общим счетом более 200
  2. Для каждого из этих клиентов, установить clear в Yes

У вас уже есть хорошее решение для первой части. Вторая часть должна быть реализована в виде отдельного 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}
    )

Ответ 4

В Mongo 4.2 теперь это можно сделать с помощью обновления с конвейером агрегации. В примере 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
  }}}}
]);