Вы можете использовать структуру агрегации для расчета агрегатов. Это более быстрая альтернатива Map/Reduce для обычных операций агрегирования. В MongoDB конвейер состоит из ряда специальных операторов, применяемых к коллекции для обработки записей данных и возврата вычисленных результатов. Операции агрегирования группируют значения из нескольких документов вместе и могут выполнять различные операции над сгруппированными данными, чтобы вернуть один результат. Дополнительные сведения см. в документации.
Рассмотрите возможность запуска следующего конвейера, чтобы получить желаемый результат:
var pipeline = [
{ "$unwind": "$userPicks" },
{
"$group": {
"_id": {
"week": "$week",
"user": "$userPicks.user"
},
"weeklyScore": {
"$sum": {
"$cond": [
{ "$eq": ["$userPicks.chosenTeam", "$winner"] },
1, 0
]
}
}
}
},
{
"$group": {
"_id": "$_id.user",
"weeklyScores": {
"$push": {
"week": "$_id.week",
"score": "$weeklyScore"
}
},
"totalScores": { "$sum": "$weeklyScore" }
}
}
];
Game.aggregate(pipeline, function(err, results){
User.populate(results, { "path": "_id" }, function(err, results) {
if (err) throw err;
console.log(JSON.stringify(results, undefined, 4));
});
})
В приведенном выше конвейере первым шагом является $unwind
оператор
{ "$unwind": "$userPicks" }
что очень удобно, когда данные хранятся в виде массива. Когда оператор раскрутки применяется к полю данных списка, он создает новую запись для каждого элемента поля данных списка, к которому применяется раскрутка. Это в основном сглаживает данные.
Это необходимая операция для следующего этапа конвейера, $group
шаг, на котором вы группируете сведенные документы по полям week
и "userPicks.user"
{
"$group": {
"_id": {
"week": "$week",
"user": "$userPicks.user"
},
"weeklyScore": {
"$sum": {
"$cond": [
{ "$eq": ["$userPicks.chosenTeam", "$winner"] },
1, 0
]
}
}
}
}
Конвейер $group
оператор похож на предложение SQL GROUP BY
. В SQL вы не можете использовать GROUP BY
, если вы не используете какую-либо из функций агрегирования. Точно так же вы должны использовать функцию агрегации и в MongoDB. Подробнее о функциях агрегации можно прочитать здесь.
В этой $group
операции, логика расчета недельного счета каждого пользователя (т. е. количества футбольных игр, которые они правильно прогнозируют каждую неделю) выполняется с помощью тернарного оператора $cond
, который принимает логическое условие в качестве первого аргумента (если), а затем возвращает второй аргумент, если оценка верна (тогда) или третий аргумент, где ложь (иначе). Это делает истинные/ложные возвраты в 1 и 0 для подачи на $sum
соответственно:
"$cond": [
{ "$eq": ["$userPicks.chosenTeam", "$winner"] },
1, 0
]
Таким образом, если в обрабатываемом документе поле "$userPicks.chosenTeam"
совпадает с полем "$winner"
, $cond
передает значение 1 сумме, иначе суммирует нулевое значение.
Второй групповой конвейер:
{
"$group": {
"_id": "$user",
"weeklyScores": {
"$push": {
"week": "$_id.week",
"score": "$weeklyScore"
}
},
"totalScores": { "$sum": "$weeklyScore" }
}
}
берет документы из предыдущего конвейера и группирует их дальше по полю user
и вычисляет другой агрегат, то есть общий балл, используя $sum
. В рамках того же конвейера вы можете агрегировать список еженедельных оценок, используя $push
, который возвращает массив значений выражений для каждой группы.
Здесь следует отметить одну вещь: при выполнении конвейера MongoDB передает операторы друг другу. «Канала» здесь имеет значение Linux: вывод оператора становится вводом следующего оператора. Результатом работы каждого оператора является новая коллекция документов. Таким образом, Mongo выполняет указанный выше конвейер следующим образом:
collection | $unwind | $group | $group => result
Теперь, когда вы запускаете конвейер агрегации в Mongoose, результаты будут иметь ключ _id
, который является идентификатором пользователя, и вам нужно заполнить результаты в этом поле, т.е. Mongoose выполнит «присоединение» к коллекции пользователей и вернет документы с схема пользователя в результатах.
В качестве примечания: чтобы помочь понять конвейер или отладить его, если вы получите неожиданные результаты, запустите агрегацию только с первым оператором конвейера. Например, запустите агрегацию в оболочке mongo как:
db.games.aggregate([
{ "$unwind": "$userPicks" }
])
Проверьте результат, чтобы увидеть, правильно ли деконструирован массив userPicks
. Если это дает ожидаемый результат, добавьте следующее:
db.games.aggregate([
{ "$unwind": "$userPicks" },
{
"$group": {
"_id": {
"week": "$week",
"user": "$userPicks.user"
},
"weeklyScore": {
"$sum": {
"$cond": [
{ "$eq": ["$userPicks.chosenTeam", "$winner"] },
1, 0
]
}
}
}
}
])
Повторяйте шаги, пока не дойдете до последнего шага конвейера.
person
chridam
schedule
13.09.2016