Группировать записи по месяцам и считать их - Пн goose, nodeJs, mongoDb - PullRequest
0 голосов
/ 06 апреля 2020

Мне нужно запросить в базе данных (пн goose) и получить количество продаж, совершенных каждый месяц в году для одного конкретного продукта (в течение одного года).

Я новичок в node и mongoDb, и у меня есть «фиктивное» решение, в котором я запрашиваю в базе данных и получаю все результаты по одному продукту, а затем использую 3 цикла для разделения результатов по месяцам, но Я думаю, что он использует больше ресурсов, чем следовало бы, и он будет использовать даже больше, если он будет заполнен большим количеством данных, поэтому мне нужна помощь в создании запроса к базе данных, чтобы решить эту проблему.

Вот часть моего кода, которая делает это:

Предполагая, что результаты требуются от 17-02-2020 до 17-02-2019 , я знаю, что если это с января до декабрь это будет go в один l oop даром, но у меня есть другая часть кода, которая контролирует, если требуется результат за 1 год, например: 01-01-2020 до 31-12-2020 он не выполнит приведенный ниже код, этот код, о котором я говорю, имеет только один l oop lol.

let startTime = performance.now();
Sales.find({productId:req.params.productId, "created_at": { "$gte": oneYearFromNow, "$lte": dateNow}}).then(result => {
        let newMonthsArray= new Array();
        let monthsArray = ['January','February','March','April','May','June','July','August','September','October', 'November','December'];
        let months = {};
        for(let i=parseInt(req.params.startDate.substring(5,7))-1; i<12; i++){
            let year = parseInt(req.params.startDate.substring(0,4))-1;
            let month = parseInt(req.params.startDate.substring(5,7));
            newMonth = monthsArray[i] + '-' + year;
            newMonthsArray.push(newMonth);
            months[newMonth] = 0; 
        }

        for(let i=0; i<parseInt(req.params.startDate.substring(5,7)); i++){
            let year = parseInt(req.params.startDate.substring(0,4));
            let month = parseInt(req.params.startDate.substring(5,7));
            newMonth = monthsArray[i] + '-' + year;
            newMonthsArray.push(newMonth);
            months[newMonth] = 0; 
          }

        for(i=0; i<result.length; i++){
            let getDate = result[i].created_at.toISOString();
            let year = getDate.substring(0,4);
            let month = parseInt(getDate.substring(5,7));
            let monthName = monthsArray[month-1];
            let date =  monthName + '-' + year;
            let count = Number(months[date]) + 1;
            months[date] = count;
        }

        let endTime = performance.now();
        res.status(200).send({Data: months, 'Execution time': endTime - startTime + ' mls'});
    });

Я надеюсь, что все понятно, я думаю, что мне нужно использовать агрегацию, но я не уверен, как!

Пример данных:

{
    {
        "created_at": "2020-04-04T17:02:07.832Z",
        "updated_at": "2020-04-04T17:02:07.832Z",
        "_id": "5e88bdcda3080736ac70f9c1",
        "price": 16800,
        "productId": "5e88bf90b9e5102ae46b154e",
        "__v": 0
    },
    {
        "created_at": "2020-04-04T17:02:07.832Z",
        "updated_at": "2020-04-04T17:02:07.832Z",
        "_id": "5e88bdf9a3080736ac70f9c2",
        "price": 12800,
        "productId": "5e88bf90b9e5102ae46b154e",
        "__v": 0
    }
}

Желаемый результат:

enter image description here

Ответы [ 2 ]

1 голос
/ 06 апреля 2020

Вот запрос агрегации, который возвращает ожидаемый результат. Некоторые примеры документов:

[
  { created_at: "2020-04-04T17:02:07.832Z", productId: 1 },
  { created_at: "2020-02-01T17:02:07.832Z", productId: 1 },
  { created_at: "2020-02-19T17:02:07.832Z", productId: 1 },
  { created_at: "2019-05-22T17:02:07.832Z", productId: 1 },
  { created_at: "2020-01-15T17:02:07.832Z", productId: 1 },
  { created_at: "2020-01-30T17:02:07.832Z", productId: 2 },  // not selected
  { created_at: "2019-03-15T17:02:07.832Z", productId: 1 }   // not selected
]

Входные переменные и агрегация:

let TODAY = "2020-04-06T23:59:59"
let YEAR_BEFORE = "2019-04-07T00:00:00"
let req = { params: { productId: 1 } }
const monthsArray = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]

db.sales.aggregate( [
  { 
      $match: { 
          productId: req.params.productId, 
          created_at: { $gte: YEAR_BEFORE, $lte: TODAY }
      }
  },
  { 
      $group: {
          _id: { "year_month": { $substrCP: [ "$created_at", 0, 7 ] } }, 
          count: { $sum: 1 }
      } 
  },
  {
      $sort: { "_id.year_month": 1 }
  },
  { 
      $project: { 
          _id: 0, 
          count: 1, 
          month_year: { 
              $concat: [ 
                 { $arrayElemAt: [ monthsArray, { $subtract: [ { $toInt: { $substrCP: [ "$_id.year_month", 5, 2 ] } }, 1 ] } ] },
                 "-", 
                 { $substrCP: [ "$_id.year_month", 0, 4 ] }
              ] 
          }
      } 
  },
  { 
      $group: { 
          _id: null, 
          data: { $push: { k: "$month_year", v: "$count" } }
      } 
  },
  {
      $project: { 
          data: { $arrayToObject: "$data" }, 
          _id: 0 
      } 
  }
] )

Вывод:

{
        "data" : {
                "May-2019" : 1,
                "January-2020" : 1,
                "February-2020" : 2,
                "April-2020" : 1
        }
}



Вот обновленная агрегация .

Обратите внимание на следующие изменения: (1) новые константы FIRST_MONTH и LAST_MONTH, (2) изменили имя переменной monthsArray на MONTHS_ARRAY, (3) добавили 3 новых этапа конвейера.

Первые на двух этапах конвейера (новый) построить шаблон со всеми месяцами (охватывающий диапазон дат от и до ввода). Третий новый этап объединяет шаблон с выходными данными, полученными из предыдущей агрегации.

const FIRST_MONTH = 1
const LAST_MONTH = 12
const MONTHS_ARRAY = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]

let TODAY = "2020-04-06T23:59:59"
let YEAR_BEFORE = "2019-04-07T00:00:00"

db.sales.aggregate( [
  { 
      $match: { 
          productId: req.params.productId, 
          created_at: { $gte: YEAR_BEFORE, $lte: TODAY }
      }
  },
  { 
      $group: {
          _id: { "year_month": { $substrCP: [ "$created_at", 0, 7 ] } }, 
          count: { $sum: 1 }
      } 
  },
  {
      $sort: { "_id.year_month": 1 }
  },
  { 
      $project: { 
          _id: 0, 
          count: 1, 
          month_year: { 
              $concat: [ 
                 { $arrayElemAt: [ monthsArray, { $subtract: [ { $toInt: { $substrCP: [ "$_id.year_month", 5, 2 ] } }, 1 ] } ] },
                 "-", 
                 { $substrCP: [ "$_id.year_month", 0, 4 ] }
              ] 
          }
      } 
  },
  { 
      $group: { 
          _id: null, 
          data: { $push: { k: "$month_year", v: "$count" } }
      } 
  },
  { 
      $addFields: { 
          start_year: { $substrCP: [ YEAR_BEFORE, 0, 4 ] }, 
          end_year: { $substrCP: [ TODAY, 0, 4 ] },
          months1: { $range: [ { $toInt: { $substrCP: [ YEAR_BEFORE, 5, 2 ] } }, { $add: [ LAST_MONTH, 1 ] } ] },
          months2: { $range: [ FIRST_MONTH, { $add: [ { $toInt: { $substrCP: [ TODAY, 5, 2 ] } }, 1 ] } ] }
      } 
  },
  { 
      $addFields: { 
          template_data: { 
              $concatArrays: [ 
                  { $map: { 
                       input: "$months1", as: "m1",
                       in: {
                           count: 0,
                           month_year: { 
                               $concat: [ { $arrayElemAt: [ MONTHS_ARRAY, { $subtract: [ "$$m1", 1 ] } ] }, "-",  "$start_year" ] 
                           }                                            
                       }
                  } }, 
                  { $map: { 
                       input: "$months2", as: "m2",
                       in: {
                           count: 0,
                           month_year: { 
                               $concat: [ { $arrayElemAt: [ MONTHS_ARRAY, { $subtract: [ "$$m2", 1 ] } ] }, "-",  "$end_year" ] 
                           }                                            
                       }
                  } }
              ] 
         }
      }
  },
  { 
      $addFields: { 
          data: { 
             $map: { 
                 input: "$template_data", as: "t",
                 in: {   
                     k: "$$t.month_year",
                     v: { 
                         $reduce: { 
                             input: "$data", initialValue: 0, 
                             in: {
                                 $cond: [ { $eq: [ "$$t.month_year", "$$this.k"] },
                                              { $add: [ "$$this.v", "$$value" ] },
                                              { $add: [ 0, "$$value" ] }
                                 ]
                             }
                         } 
                     }
                 }
              }
          }
      }
  },
  {
      $project: { 
          data: { $arrayToObject: "$data" }, 
          _id: 0 
      } 
  }
] )

Выходные данные:

{
        "data" : {
                "April-2019" : 0,
                "May-2019" : 1,
                "June-2019" : 0,
                "July-2019" : 0,
                "August-2019" : 0,
                "September-2019" : 0,
                "October-2019" : 0,
                "November-2019" : 0,
                "December-2019" : 0,
                "January-2020" : 1,
                "February-2020" : 2,
                "March-2020" : 0,
                "April-2020" : 1
        }
}
1 голос
/ 06 апреля 2020

Да, вы правы, что вам нужно использовать агрегацию. Это должно работать:

// NOTE: It is important that the the month strings start from the second element in this array  
// becuase the $month operator returns month values as numbers from 1 to 12.
const monthStrings = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
Sales.aggregate([
  {
    $match: {
      // Match only salses with a specific productId
      productId: req.params.productId,
      // Match only salses that fufils the date constraint below
      $expr: {
        $and: [
          { $gt: ["$created_at", oneYearFromNow] },
          { $lt: ["$created_at", dateNow] }
        ],
      }
    }
  },
  {
    $group: {
       // Group by both month and year of the sale
      _id: {
        month: { $month: "$created_at" },
        year: { $year: "$created_at" },
      },
      // Count the no of sales
      count: {
        $sum: 1
      }
    }
  },
  // Adding a project here to just to format the group date better
  {
    $project: {
      _id: {
        $concat: [
          {
            $arrayElemAt: [
              monthStrings,
              "$_id.month"
            ]
          },
          "-",
          "$_id.year"
        ]
      },
      count: 1,
    }
  }
])

Вы можете протестировать конвейер агрегации на этой площадке .

Выходными данными будет массив таких объектов:

{ "_id": *,  "count": * }

Где значение _id представляет собой строку в формате <month>-<year> (например, April-2019), представляющую месяц и год продажи. Значение счета - это количество продаж, которые произошли в тот месяц / год.

Я использовал комбинацию этапов $ match, $ group и $ project в конвейере агрегации, вы можете найти более подробную информацию о том, как работают эти этапы. здесь , здесь и здесь соответственно.

...