Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongodb Aggregate - Grouping by hour for today

I was wondering if someone could help me get my aggregation function right. I'm trying to count load of visits of my Fitness. I have Visits table where every visit (from, to) dates are saved. Also sub-visits to some services saved too.

[{
  "from": ISODate("2020-01-17T10:23:27.645Z"),
  "to": ISODate("2020-01-17T12:23:28.760Z"),
  "visits": [
    {
      "from": ISODate("2020-01-17T10:23:27.646Z"),
      "to": ISODate("2020-01-17T10:30:28.760Z"),
      "service": ObjectId("5e05f17d6b7f7920d4a62403")
    },
    {
      "from": ISODate("2020-01-17T10:30:29.760Z"),
      "to": ISODate("2020-01-17T12:23:28.760Z"),
      "service": ObjectId("5d05f17dt57f7920d4a62404")
    }
  ],
  ...
},
{
  "from": ISODate("2020-01-17T10:40:00.000Z"),
  "to": ISODate("2020-01-17T11:30:28.760Z"),
  "visits": [
    {
      "from": ISODate("2020-01-17T10:40:00.000Z"),
      "to": ISODate("2020-01-17T11:30:28.760Z"),
      "service": ObjectId("h505f17s6b2f7920d4a6295y")
    }
  ],
  ...
}
]

I wanted to reach the result for today from 00:00 if current time is 13:35 then get until 13:00 like :

[{
  'date': '2020-01-18T00:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T00:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T00:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T01:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T02:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T03:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T04:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T05:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T06:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T07:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T08:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T09:00:0.000Z'
  'visits': 0
},
{
  'date': '2020-01-18T010:00:0.000Z'
  'visits': 2
},
{
  'date': '2020-01-18T11:00:0.000Z'
  'visits': 2
},
{
  'date': '2020-01-18T12:00:0.000Z'
  'visits': 1
},
{
  'date': '2020-01-18T13:00:0.000Z'
  'visits': 0
}]

Tried too many examples, but nothing worked, Please help !

like image 340
Azamat Ahunjanov Avatar asked Nov 25 '25 10:11

Azamat Ahunjanov


1 Answers

EXPLANATION

  1. If we use $$NOW, we get current date. If you setup currDate, today, nextDay manually, MongoDB aggregation will limit desired values.

  2. Now, we calculate how many hours from today (yyyy-MM-dd 00:00:00) - currDate (yyyy-MM-dd 13:35:00) ~ 13 hours and with $range operator create array

  3. We flatten interval and apply filtering

You may use such query:

db.collection.aggregate([
  {
    $addFields: {
      nextDay: {
        $add: [
          {
            $dateFromString: {
              dateString: {
                $substr: [
                  {
                    $toString: "$$NOW"
                  },
                  0,
                  10
                ]
              },
              format: "%Y-%m-%d"
            }
          },
          {
            $multiply: [
              24,
              60,
              60,
              1000
            ]
          }
        ]
      },
      today: {
        $dateFromString: {
          dateString: {
            $substr: [
              {
                $toString: "$$NOW"
              },
              0,
              10
            ]
          },
          format: "%Y-%m-%d"
        }
      },
      currDate: {
        $dateFromString: {
          dateString: {
            $substr: [
              {
                $toString: "$$NOW"
              },
              0,
              13
            ]
          },
          format: "%Y-%m-%dT%H",
          timezone: "-01"
        }
      }
    }
  },
  {
    $addFields: {
      interval: {
        $range: [
          0,
          {
            $add: [
              {
                $divide: [
                  {
                    $subtract: [
                      "$currDate",
                      "$today"
                    ]
                  },
                  {
                    $multiply: [
                      60,
                      60,
                      1000
                    ]
                  }
                ]
              },
              1
            ]
          },
          1
        ]
      }
    }
  },
  {
    $unwind: "$interval"
  },
  {
    $addFields: {
      date: {
        $add: [
          "$today",
          {
            $multiply: [
              "$interval",
              60,
              60,
              1000
            ]
          }
        ]
      },
      nextHour: {
        $add: [
          "$today",
          {
            $multiply: [
              {
                $add: [
                  "$interval",
                  1
                ]
              },
              60,
              60,
              1000
            ]
          }
        ]
      }
    }
  },
  {
    $project: {
      _id: 0,
      date: 1,
      today: 1,
      nextDay: 1,
      visits: {
        $reduce: {
          input: "$visits",
          initialValue: 0,
          in: {
            $add: [
              "$$value",
              {
                $cond: [
                  {
                    $or: [
                      {
                        $and: [
                          {
                            $gte: [
                              "$$this.from",
                              "$date"
                            ]
                          },
                          {
                            $lte: [
                              "$$this.from",
                              "$nextHour"
                            ]
                          }
                        ]
                      },
                      {
                        $and: [
                          {
                            $lte: [
                              "$date",
                              "$$this.to"
                            ]
                          },
                          {
                            $gte: [
                              "$date",
                              "$$this.from"
                            ]
                          }
                        ]
                      }
                    ]
                  },
                  1,
                  0
                ]
              }
            ]
          }
        }
      }
    }
  },
  {
    $match: {
      $expr: {
        $and: [
          {
            $gte: [
              "$date",
              "$today"
            ]
          },
          {
            $lte: [
              "$date",
              "$nextDay"
            ]
          },

        ]
      }
    }
  },
  {
    $group: {
      _id: "$date",
      visits: {
        $sum: "$visits"
      }
    }
  },
  {
    $sort: {
      _id: 1
    }
  }
])

MongoPlayground

For JS setup, click here

You may try this solution if you want more elegant way

like image 122
Valijon Avatar answered Nov 26 '25 23:11

Valijon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!