Mongoose return grouped by date

My model is the following :

const scheduleTaskSchema = new Schema({
  activity: { type: Object, required: true },
  date: { type: Date, required: true },
  crew: Object,
  vehicle: Object,
  pickups: Array,
  details: String,
});

pickups is an array of objects with the following structure :

{
time : //iso date,
...rest //irrelevant
}

i want to return my data grouped by date,and also every group within has to be sorted by pickup[0].time so it will finally return something like this example ( thats the data structure returned by lodash’s _groupBy function,maybe mongoose returns something else? ) :

 {
  "2022-08-28T00:00:00.000+00:00": [
    {
      date: "2022-08-28T00:00:00.000+00:00",
      pickups: [
        {
          time: "2022-08-28T07:30:00.000Z",
          ...rest //irrelevant
        }
      ],
      ...rest //irrelevant
    },
    {
      date: "2022-08-28T00:00:00.000+00:00",
      pickups: [
        {
          time: "2022-08-28T09:30:00.000Z",
          ...rest //irrelevant
        }
      ],
      ...rest //irrelevant
    }
  ]
  ,
   "2022-08-29T00:00:00.000+00:00": [
    {
      date: "2022-08-29T00:00:00.000+00:00",
      pickups: [
        {
          time: "2022-08-29T10:00:00.000Z",
          ...rest //irrelevant
        }
      ],
      ...rest //irrelevant
    },
    {
      date: "2022-08-29T00:00:00.000+00:00",
      pickups: [
        {
          time: "2022-08-29T11:30:00.000Z",
          ...rest //irrelevant
        }
      ],
      ...rest //irrelevant
    }
  ]
}

>Solution :

You need to use the aggregation framework for this.
Assuming the dates are exactly the same (down to the millisecond), your code would look something like this.

const scheduledTasksGroups = await ScheduledTask.aggregate([{
  $group: {
    _id: '$date',
    scheduledTasks: { $push: '$$ROOT' }
  }
}]);

The output will be something like:

[
  { _id: "2022-08-29T10:00:00.000Z", scheduledTasks: [...] },
  { _id: "2022-08-28T10:00:00.000Z", scheduledTasks: [...] }
]

If you want to group by day instead of by millisecond, your pipeline would look like this:

const scheduledTasksGroups = await ScheduledTask.aggregate([{
  $group: {
    // format date first to `YYYY-MM-DD`, then group by the new format
    _id: { $dateToString: { format: '%Y-%m-%d', date: '$date' } },
    scheduledTasks: { $push: '$$ROOT' }
  }
}]);

For what it’s worth, this is a MongoDB feature, the grouping happens on the MongoDB server side; mongoose doesn’t do anything special here; it just sends the command to the server. Then the server is responsible for grouping the data and returning them back.

Also, keep in mind that mongoose does not cast aggregation pipelines by default, but this plugin makes mongoose cast automatically whenever possible.

Leave a Reply