获取最近 30 天的 Mongo 记录,按日期求和,并填充范围内缺失的日期

Fetching last 30 days of Mongo records, summing by date, and filling in missing dates in range

我有一个 Mongo 数据库,里面装满了“事件”记录,看起来像这样:

{
    timestamp: 2022-03-15T22:11:34.711Z,
    _id: new ObjectId("62310f16b0d71321e887a905")
}

使用 NodeJs 服务器,我需要获取最近 30 天的事件,grouped/summed 按日期,这 30 天内没有记录的任何日期都需要填充 0。

使用此代码我可以获得正确的事件,grouped/summed 按日期:

Event.aggregate( [
      { 
          $match:  {   
              timestamp: {
                  $gte: start,
                 $lte: end,
             }
        } 
    },
    {
        $project: {
            date: {
               $dateToParts: { date: "$timestamp" }
            },
         }
      },
      {
         $group: {
            _id: {
               date: {
                  year: "$date.year",
                  month: "$date.month",
                  day: "$date.day"
               }
            },
            "count": { "$sum": 1 }
         }
    }
] )

这将 return 像这样:

[
    {
        "_id": {
            "date": {
                "year": 2022,
                "month": 3,
                "day": 14
            }
        },
        "count": 3
    },
    {
        "_id": {
            "date": {
                "year": 2022,
                "month": 3,
                "day": 15
            }
        },
        "count": 8
    },
]

我也有这个 Javascript 代码来生成最后 30 天的日期:

 const getDateRange = (start, end)  => {
    const arr = [];
    for(let dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)){
        arr.push(new Date(dt));
    }
    return arr;
};
const subtractDays = (date, days) => {
    return new Date(date.getTime() - (days * 24 * 60 * 60 * 1000));
}

const end = new Date(); 
const start = subtractDays(end, 30);
const range = getDateRange(start, end);

其中 return 是这样的:

[
  2022-03-09T01:13:10.769Z,
  2022-03-10T01:13:10.769Z,
  2022-03-11T01:13:10.769Z,
  2022-03-12T01:13:10.769Z,
  2022-03-13T01:13:10.769Z,
  ...
]

似乎我拥有所有的部分,但我无法将所有这些组合在一起以有效地完成我需要的事情。任何朝着正确方向的推动将不胜感激。

每当需要使用 date/time 算法时,我都会推荐一个像 moment.js

这样的库
const end = moment().startOf('day').toDate(); 
const start = moment().startOf('day').subtract(30, 'day').toDate();

在MongoDB 5.0版本中你可以使用$dateTrunc(),它比$dateToParts{ year: "$date.year", month: "$date.month", day: "$date.day" }

更短

您需要将所有数据放入一个数组 ({$group: {_id: null, data: { $push: "$$ROOT" }}) 中,然后在缺少的元素处使用 $ifNull:

event.aggregate([
   {
      $match: {
         timestamp: { $gte: start, $lte: end }
      }
   },
   {
      $group: {
         _id: { $dateTrunc: { date: "$timestamp", unit: "day" } },
         count: { $sum: 1 }
      }
   },
   { $project: {timestamp: "$_id", count: 1, _id: 0} },
   {
      $group: {
         _id: null,
         data: { $push: "$$ROOT" }
      }
   },
   {
      $set: {
         data: {
            $map: {
               input: { $range: [0, 30] },
               as: "i",
               in: {
                  $let: {
                     vars: {
                        day: { $dateAdd: { startDate: start, amount: "day", unit: "$$i" } }
                     },
                     in: {
                        $ifNull: [
                           {
                              $first: {
                                 $filter: {
                                    input: "$data",
                                    cond: { $eq: ["$$this.timestamp", "$$day"] }
                                 }
                              }
                           },
                           { timestamp: "$$day", count: 0 }
                        ]
                     }
                  }
               }
            }
         }
      }
   },
   { $unwind: "$data" }
])

$range 运算符仅支持整数值,这就是使用 $let 的原因。否则,如果您更喜欢使用外部生成的范围,则为

{
   $set: {
      data: {
         $map: {
            input: range,
            as: "day",
            in: {
               $ifNull: [
                  {
                     $first: {
                        $filter: {
                           input: "$data",
                           cond: { $eq: ["$$this.timestamp", "$$day"] }
                        }
                     }
                  },
                  { timestamp: "$$day", count: 0 }
               ]
            }
         }
      }
   }
}

对于 MongoDB 版本 5.1,您可以查看 $densify

如果您使用的是 MongoDB 5.1 版或更高版本,请使用聚合阶段 densify。但是对于低版本,可以使用下面的查询。

db.collection.aggregate([
  {
    $match: {
      timestamp: {
        $gte: {
          "$date": "2022-03-01T00:00:00.000Z"
        },
        $lte: {
          "$date": "2022-03-31T23:59:59.999Z"
        },
      }
    }
  },
  {
    $project: {
      date: {
        $dateToParts: {
          date: "$timestamp"
        }
      },
    }
  },
  {
    $group: {
      _id: {
        date: {
          year: "$date.year",
          month: "$date.month",
          day: "$date.day"
        }
      },
      "count": {
        "$sum": 1
      }
    }
  },
  {
    "$group": {
      "_id": null,
      "originData": {
        "$push": "$$ROOT"
      }
    }
  },
  {
    "$project": {
      "_id": 0,
      "data": {
        "$concatArrays": [
          {
            "$map": {
              "input": {
                "$range": [
                  0,
                  30,
                  1
                ]
              },
              "in": {
                "$let": {
                  "vars": {
                    "date": {
                      "$add": [
                        {
                          "$date": "2022-03-01T00:00:00.000Z"
                        },
                        {
                          "$multiply": [
                            "$$this",
                            86400000
                          ]
                        }
                      ]
                    }
                  },
                  "in": {
                    "_id": {
                      "date": {
                        "day": {
                          "$dayOfMonth": "$$date"
                        },
                        "month": {
                          "$month": "$$date"
                        },
                        "year": {
                          "$year": "$$date"
                        }
                      }
                    },
                    "count": 0
                  }
                }
              }
            }
          },
          "$originData"
        ]
      }
    }
  },
  {
    "$unwind": "$data"
  },
  {
    $group: {
      _id: {
        date: {
          year: "$data._id.date.year",
          month: "$data._id.date.month",
          day: "$data._id.date.day"
        }
      },
      "count": {
        "$sum": "$data.count"
      }
    }
  },
  {
    "$sort": {
      "_id.date.year": 1,
      "_id.date.month": 1,
      "_id.date.day": 1
    }
  }
])

Link 到在线游乐场。 https://mongoplayground.net/p/5I0I04HoHXm