Агрегация с обновлением в 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
Вам нужно сделать это в два этапа:
- Определите клиентов (
cust_id
) с общим счетом более 200 - Для каждого из этих клиентов, установить
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}
)
Ответ 3
В MongoDB 2.6. Можно будет написать результат запроса агрегации с той же командой.
Дополнительная информация здесь: http://docs.mongodb.org/master/reference/operator/aggregation/out/
Ответ 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
}}}}
]);