#javascript #node.js #mongodb #mongoose #aggregation-framework
#javascript #node.js #mongodb #мангуст #агрегация-фреймворк
Вопрос:
Мне было поручено генерировать средние значения за день, неделю, месяц и год для довольно большого набора документов в MongoDB.
У всех заданий есть created
поле, и мне нужно основывать средние значения на outputs
массиве…
Вот как выглядит документ:
{
__v: 0,
_id: ObjectId("535837911393fd0200d8e1eb"),
created: ISODate("2014-04-23T21:58:41.446Z"),
output: [
{
ref: {
img: false
},
type: "image/png",
methods: [
{
options: {
height: 200,
width: 200
},
method: "resize"
}
]
},
{
ref: {
img: false
},
type: "image/png",
methods: [
{
options: {
height: 400,
width: 400
},
method: "resize"
}
]
}
]
}
И вот как выглядит мой текущий скрипт:
JobModel.aggregate([
{
$unwind: '$output'
},
{
$group: {
_id: { $dayOfYear: '$created' },
day: { $sum: 1 }
}
},
{
$group: {
_id: null,
avgDay: { $avg: '$day' }
}
},
{
$project: {
_id: 0,
average: {
day: '$avgDay'
}
}
}
],
function(err, data) {
if (err) {
console.log(err);
return;
}
res.send(data);
next();
});
Кажется, я не могу определить правильный порядок для этого. Есть предложения?
Ответ №1:
На самом деле не уверен, что вам нужно здесь. Вы говорите, что вам нужны «множественные» средние, но это поднимает вопрос о «множественности» на какой основе? Средние «выходные» записи за отдельный день будут отличаться от средних выходных записей в месяц или даже в среднем за день в месяц. Таким образом, масштаб меняется с каждым выбором и на самом деле не является одним запросом для «ежедневно», «ежемесячно» и «ежегодно»
Мне кажется, что у вас действительно были «дискретные» итоги, к которым лучше всего подходить, сначала найдя «размер» выходных записей, а затем применив среднее значение для каждой шкалы:
JobModel.aggregate(
[
{ "$unwind": "$output" },
// Count the array entries on the record
{ "$group": {
"_id": "$_id",
"created": { "$first": "$created" },
"count": { "$sum": 1 }
}},
// Now get the average per day
{ "$group": {
"_id": { "$dayOfYear": "$created" },
"avg": { "$avg": "$count" }
}}
],
function(err,result) {
}
);
Или на самом деле с MongoDB 2.6 и выше вы можете просто использовать $size
оператор для массива:
JobModel.aggregate(
[
// Now get the average per day
{ "$group": {
"_id": { "$dayOfYear": "$created" },
"avg": { "$avg": { "$size": "$output" } }
}}
],
function(err,result) {
}
);
Итак, логично запустить каждый из них в пределах требуемого $match
диапазона, кроме вашего ключа агрегации «день», «месяц» или «год»
Вы могли бы сделать что-то вроде объединения среднесуточных значений за день со среднесуточным значением за месяц, а затем ежедневно в течение года путем объединения результатов в массивы, в противном случае вы бы просто выбрасывали элементы, что можно делать поочередно, если вы «просто» хотели получить среднесуточное значение за год, но как полные результаты:
JobModel.aggregate(
[
// Now get the average per day
{ "$group": {
"_id": {
"year": { "$year": "$created" },
"month": { "$month": "$created" },
"day": { "$dayOfYear": "$created" }
},
"dayAvg": { "$avg": { "$size": "$output" } }
}},
// Group for month
{ "$group": {
"_id": {
"year": "$_id.year",
"month": "$_id.month"
},
"days": {
"$push": {
"day": "$_id.day",
"avg": "$dayAvg"
}
},
"monthAvg": { "$avg": "$dayAvg" }
}},
// Group for the year
{ "$group": {
"_id": "$_id.year",
"daily": { "$avg": "$monthAvg" },
"months": {
"$push": {
"month": "$_id.month",
"daily": "$monthAvg",
"days": "$days"
}
}
}}
],
function(err,result) {
}
);
Однако вы хотите применить это, но главное, чего не хватает в вашем примере, — это найти «размер» или «количество» исходного «выходного» массива для каждого документа, из которого можно получить среднее значение.
Комментарии:
1. Очень полезно. Я пытаюсь получить средние значения за время жизни пользователя. Таким образом, это будет первое задание, когда-либо отправленное, до последнего отправленного задания. Есть ли шанс, что вы можете привести пример с этим? Спасибо!
2. @NickParsons Не совсем понятно из контекста вашего вопроса. Что такое «пользователь»? Я не вижу в ваших методах ничего, что указывало бы на «пользователя» как такового. Похоже, что эти данные каким-то образом являются «дочерними» для «пользователя». Без ключа «user» для этих данных ваш единственный вариант — выбрать
_id
значения этой коллекции с помощью$in
. Обычно лучше, если у вас есть другой вопрос, тогда вы задаете отдельный вопрос. Это не путает приведенные ответы и позволяет вам четко сформулировать свои намерения.3. @NielLunn Извините, наличие пользователя сбивало с толку. То, что я пытаюсь сделать, это сгенерировать средние значения между диапазоном дат. Например, я хочу получить все задания за последние 30 дней, используя созданное время. Затем я хочу получить средние задания за каждый день, час, минуту и секунду. Мне нужно учитывать дни, когда документа нет.
4. @NickParsons Как я уже упоминал, я думаю, будет справедливо сказать, что на ваш вопрос здесь дан ответ. Вам было дано несколько указаний о том, как подойти к этому, а также исправлено в подходе, который вы использовали. Я также заявил, что различные «временные рамки», как правило, невозможны в одном запросе, если только в контексте приведенного примера. Вы меняете контекст своего вопроса, и, хотя он действителен как вопрос, на самом деле это другой вопрос. Вам необходимо опубликовать его как таковой, чтобы избежать путаницы в будущем для тех, кто ссылается на этот вопрос и ответ.