Вычисление нескольких средних с использованием агрегации MongoDB

#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 Как я уже упоминал, я думаю, будет справедливо сказать, что на ваш вопрос здесь дан ответ. Вам было дано несколько указаний о том, как подойти к этому, а также исправлено в подходе, который вы использовали. Я также заявил, что различные «временные рамки», как правило, невозможны в одном запросе, если только в контексте приведенного примера. Вы меняете контекст своего вопроса, и, хотя он действителен как вопрос, на самом деле это другой вопрос. Вам необходимо опубликовать его как таковой, чтобы избежать путаницы в будущем для тех, кто ссылается на этот вопрос и ответ.