Golang MongoDB驱动按时间范围查询嵌套预约数组方案
MongoDB嵌套预约数组时间范围过滤实现方案
文档结构说明
- booking根文档字段:
_id、companyID、createdAt、updatedAt、appointments(预约嵌套数组)、title - appointments子文档字段:
id、startTime、endTime、createdAt、companyID、bookingItemID - 需求:查询指定companyID下,appointments数组中预约时间落在指定时间窗口内的booking文档,返回结果仅保留符合时间要求的预约子文档,替代原有内存遍历逻辑,规避空指针异常。
注意:若你的集合中字段实际为问题描述里拼写的
apoinments(缺失字母p),将以下代码中所有对应字段名替换为实际存储值即可,示例统一使用标准拼写appointments。
实现思路
直接通过MongoDB聚合管道在数据库层完成过滤,避免内存遍历的额外开销和空指针风险,管道分为两个核心阶段:
$match阶段:前置过滤根文档,匹配指定companyID,同时通过$elemMatch粗筛至少存在一条符合时间要求的预约,排除无匹配数据的文档,减少后续计算量$addFields阶段:用$filter算子对appointments数组做条件过滤,覆盖原有数组字段,仅保留符合时间范围的预约子文档
原生MongoDB查询示例
// 替换为实际查询参数 const targetCompanyID = "目标公司ID"; const timeWindowStart = new Date("时间窗口开始时间"); const timeWindowEnd = new Date("时间窗口结束时间"); db.bookings.aggregate([ { $match: { companyID: targetCompanyID, appointments: { $elemMatch: { // 预约完全落在时间窗口内的判断条件 startTime: { $gte: timeWindowStart }, endTime: { $lte: timeWindowEnd } // 若业务需要预约与时间窗口有交集即可,替换为以下条件 // startTime: { $lt: timeWindowEnd }, // endTime: { $gt: timeWindowStart } } } } }, { $addFields: { appointments: { $filter: { input: "$appointments", cond: { $and: [ { $gte: ["$$this.startTime", timeWindowStart] }, { $lte: ["$$this.endTime", timeWindowEnd] } // 交集判断对应替换为 // { $lt: ["$$this.startTime", timeWindowEnd] }, // { $gt: ["$$this.endTime", timeWindowStart] } ] } } } } } ])
Golang驱动实现代码
package dao import ( "context" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // Booking 根文档结构体,bson tag与MongoDB字段一一对应 type Booking struct { ID string `bson:"_id"` CompanyID string `bson:"companyID"` CreatedAt time.Time `bson:"createdAt"` UpdatedAt time.Time `bson:"updatedAt"` Appointments []Appointment `bson:"appointments"` Title string `bson:"title"` } // Appointment 预约子文档结构体 type Appointment struct { ID string `bson:"id"` StartTime time.Time `bson:"startTime"` EndTime time.Time `bson:"endTime"` CreatedAt time.Time `bson:"createdAt"` CompanyID string `bson:"companyID"` BookingItemID string `bson:"bookingItemID"` } // GetBookingsInTimeRange 查询指定时间范围内符合条件的booking func GetBookingsInTimeRange(ctx context.Context, collection *mongo.Collection, companyID string, windowStart, windowEnd time.Time) ([]Booking, error) { // 构建聚合管道 pipeline := mongo.Pipeline{ { {Key: "$match", Value: bson.D{ {Key: "companyID", Value: companyID}, {Key: "appointments", Value: bson.D{ {Key: "$elemMatch", Value: bson.D{ {Key: "startTime", Value: bson.D{{Key: "$gte", Value: windowStart}}}, {Key: "endTime", Value: bson.D{{Key: "$lte", Value: windowEnd}}}, }}, }}, }}, }, { {Key: "$addFields", Value: bson.D{ {Key: "appointments", Value: bson.D{ {Key: "$filter", Value: bson.D{ {Key: "input", Value: "$appointments"}, {Key: "cond", Value: bson.D{ {Key: "$and", Value: bson.A{ bson.D{{Key: "$gte", Value: bson.A{"$$this.startTime", windowStart}}}, bson.D{{Key: "$lte", Value: bson.A{"$$this.endTime", windowEnd}}}, }}, }}, }}, }}, }}, }, } // 执行查询 cursor, err := collection.Aggregate(ctx, pipeline, options.Aggregate().SetAllowDiskUse(true)) if err != nil { return nil, err } defer cursor.Close(ctx) var results []Booking if err = cursor.All(ctx, &results); err != nil { return nil, err } // 兜底初始化空切片,避免后续遍历nil切片触发空指针 for idx := range results { if results[idx].Appointments == nil { results[idx].Appointments = make([]Appointment, 0) } } return results, nil }
原有内存遍历逻辑空指针常见原因
- 未校验查询返回的error,无匹配文档时直接操作空对象
- 遍历数组前未判断数组是否为nil,子文档存在null值时直接访问字段
- 时间字段为null时直接调用时间比较方法
内容的提问来源于stack exchange,提问作者user3089327
相关产品推荐
相关产品推荐

