You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB按周分组文档及按周五至周四周统计用户总时长方法

嘿,我来帮你搞定这两个MongoDB的分组统计问题,结合你给的打卡记录示例,一步步给你说清楚~

1. 如何在MongoDB中按周对文档进行分组?

如果是按**ISO标准周(周一为起始)**分组,最简单的方式是用$week获取日期的周数,再搭配$year区分不同年份的周(避免跨年时周数重复混淆)。比如针对你的打卡记录,按clock_date的周分组统计记录数量:

db.clock_records.aggregate([
  // 先提取年份和周数
  {
    $addFields: {
      year: { $year: "$clock_date" },
      week: { $week: "$clock_date" }
    }
  },
  // 按「年+周」组合分组
  {
    $group: {
      _id: { year: "$year", week: "$week" },
      total_records: { $sum: 1 },
      records: { $push: "$$ROOT" } // 可选,保留分组内的原始记录
    }
  },
  // 按时间顺序排序,方便查看
  { $sort: { "_id.year": 1, "_id.week": 1 } }
])

如果想要更直观的周起始日期(比如直接显示周一的日期),可以用$dateFromParts生成周起始时间:

db.clock_records.aggregate([
  {
    $addFields: {
      // 生成ISO周的周一起始日期
      week_start: {
        $dateFromParts: {
          year: { $year: "$clock_date" },
          week: { $week: "$clock_date" },
          weekday: 1 // 1代表周一,对应ISO周的起始规则
        }
      }
    }
  },
  {
    $group: {
      _id: "$week_start",
      total_records: { $sum: 1 }
    }
  },
  { $sort: { "_id": 1 } }
])
2. 按周统计每个用户的总工时(周起始为周五、结束为周四)

这个需求的核心是自定义周的边界,还要计算每条记录的工时再求和。我结合你的示例文档,拆解成步骤来实现:

核心难点说明

MongoDB的默认周规则是ISO标准(周一起始)或周日起始,所以我们需要手动调整日期,把每个clock_date映射到「最近的上一个周五」作为周起始——这样周五到下周四的所有日期都会归为同一周。另外,要注意MongoDB的$dayOfWeek取值是:1=周日,2=周一,6=周五,7=周六,这个很容易搞混,我会在代码里做注释。

完整聚合查询

假设你的集合名为clock_records,用户标识是user_id(如果你的数据里用_id区分用户,把代码里的$user_id换成$_id即可):

db.clock_records.aggregate([
  // 第一步:拼接完整的打卡时间,计算单条记录的工时(小时)
  {
    $addFields: {
      // 把clock_date和clock_in_time拼接成完整的ISO日期
      clock_in_full: {
        $dateFromString: {
          dateString: { $concat: [ { $dateToString: { format: "%Y-%m-%d", date: "$clock_date" } }, "T", "$clock_in_time" ] }
        }
      },
      clock_out_full: {
        $dateFromString: {
          dateString: { $concat: [ { $dateToString: { format: "%Y-%m-%d", date: "$clock_date" } }, "T", "$clock_out_time" ] }
        }
      }
    }
  },
  {
    $addFields: {
      // 计算工时:(结束时间-开始时间)转换为小时(1小时=3600000毫秒)
      hours_worked: {
        $divide: [ { $subtract: [ "$clock_out_full", "$clock_in_full" ] }, 3600000 ]
      }
    }
  },
  // 第二步:计算自定义周的起始日期(周五)
  {
    $addFields: {
      // 获取当前日期的星期几:1=周日,2=周一,6=周五,7=周六
      weekday: { $dayOfWeek: "$clock_date" },
      // 计算需要偏移的天数,把日期调整到最近的上一个周五
      offset_days: {
        $cond: {
          if: { $gte: [ "$weekday", 6 ] }, // 周五(6)或周六(7)
          then: { $subtract: [ "$weekday", 6 ] }, // 周五偏移0天,周六偏移1天(回到周五)
          else: { $add: [ "$weekday", 1 ] } // 周日到周四,偏移(weekday+1)天,回到上周五
        }
      },
      // 周起始日期:当前日期减去偏移天数,得到对应的周五
      week_start: {
        $dateSubtract: {
          startDate: "$clock_date",
          unit: "day",
          amount: "$offset_days"
        }
      }
    }
  },
  // 第三步:按用户+周起始分组,统计总工时
  {
    $group: {
      _id: {
        user_id: "$user_id",
        week_start: "$week_start"
      },
      total_hours: { $sum: "$hours_worked" },
      record_count: { $sum: 1 }, // 可选,统计该周的打卡次数
      record_details: { $push: { date: "$clock_date", hours: "$hours_worked" } } // 可选,保留每条记录的工时明细
    }
  },
  // 第四步:按用户和周排序,结果更清晰
  { $sort: { "_id.user_id": 1, "_id.week_start": 1 } }
])

针对你示例数据的验证

比如你给出的两条记录:

  • clock_date: 2018-01-13(周六,weekday=7):偏移1天,week_start为2018-01-12(周五),工时是2小时
  • clock_date: 2018-01-12(周五,weekday=6):偏移0天,week_start为2018-01-12,工时是40分钟(约0.6667小时)

分组后,这两条记录会归到同一个周,总工时为2.6667小时,符合预期。


内容的提问来源于stack exchange,提问作者Rahul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:26:07