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

Golang MongoDB驱动按时间范围查询嵌套预约数组方案

MongoDB嵌套预约数组时间范围过滤实现方案

文档结构说明

  • booking根文档字段:_id、companyID、createdAt、updatedAt、appointments(预约嵌套数组)、title
  • appointments子文档字段:id、startTime、endTime、createdAt、companyID、bookingItemID
  • 需求:查询指定companyID下,appointments数组中预约时间落在指定时间窗口内的booking文档,返回结果仅保留符合时间要求的预约子文档,替代原有内存遍历逻辑,规避空指针异常。

注意:若你的集合中字段实际为问题描述里拼写的apoinments(缺失字母p),将以下代码中所有对应字段名替换为实际存储值即可,示例统一使用标准拼写appointments。

实现思路

直接通过MongoDB聚合管道在数据库层完成过滤,避免内存遍历的额外开销和空指针风险,管道分为两个核心阶段:

  1. $match阶段:前置过滤根文档,匹配指定companyID,同时通过$elemMatch粗筛至少存在一条符合时间要求的预约,排除无匹配数据的文档,减少后续计算量
  2. $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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 14:06:22