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

基于MongoDB/Mongoose查询指定时段未占用酒店房间的实现方法

如何查询MongoDB中指定日期区间内未被占用的酒店房间(含取消预订的情况)

问题背景

你现有的Mongoose Schema将预订记录嵌套在房间文档中,需求是查询2019年12月25日至2019年12月30日期间未被有效预订的房间——这里的“有效预订”指状态非cancel且日期区间与目标时段重叠的预订,同时要把有取消预订记录的房间也纳入结果。


方案一:基于现有Schema的查询实现

不需要修改现有Schema就可以实现需求,核心思路是:找出没有任何与目标日期冲突的有效预订的房间。

具体查询代码

首先定义目标日期区间:

const targetCheckIn = new Date('2019-12-25');
const targetCheckOut = new Date('2019-12-30');

然后执行Mongoose查询:

const availableRooms = await Room.find({
  // 可选:如果需要只查询可预订且已发布的房间,加上这两个条件
  isAvailable: true,
  isPublish: true,
  $or: [
    // 情况1:房间没有任何预订记录
    { reservations: { $size: 0 } },
    // 情况2:所有预订要么是取消状态,要么与目标日期区间不重叠
    {
      reservations: {
        $not: {
          $elemMatch: {
            status: { $ne: 'cancel' }, // 排除取消的预订
            // 日期重叠判断:预订的入住日 < 目标退房日,且预订的退房日 > 目标入住日
            checkIn: { $lt: targetCheckOut },
            checkOut: { $gt: targetCheckIn }
          }
        }
      }
    }
  ]
});

逻辑解释

  • $elemMatch用于匹配数组中至少一个符合条件的子文档;$not取反,意味着不存在任何“非取消状态且与目标日期重叠”的预订。
  • 日期重叠的判断逻辑是行业通用的:只要两个区间存在交集,就视为冲突。

方案二:Schema优化建议(可选)

当前的嵌套数组Schema在房间数量少、预订记录不多的场景下完全够用,但如果你的酒店业务规模较大(比如数百间房,每间房有大量预订记录),嵌套数组会导致:

  1. 房间文档体积过大,影响查询性能
  2. 预订记录的增删改操作会锁定整个房间文档,并发效率低

优化后的Schema设计

将预订记录拆分为独立集合,用ObjectId关联房间:

// Reservation 独立集合
const { Schema, model } = require('mongoose');

const reservationSchema = new Schema({
  checkIn: { type: Date, required: true },
  checkOut: { type: Date, required: true },
  status: { type: String, required: true, enum: ['pending', 'cancel', 'approved', 'active', 'completed'] },
  room: { type: Schema.Types.ObjectId, ref: 'Room', required: true } // 关联Room的ID
});
module.exports = model('Reservation', reservationSchema);
// Room 集合(移除嵌套的reservations数组)
const roomSchema = new Schema(
  {
    title: { type: String, required: true },
    slug: { type: String },
    description: { type: String, required: true },
    capacity: { adults: { type: Number, required: true }, childs: { type: Number, default: 0 } },
    roomPrice: { type: Number, required: true },
    gallery: [ { type: String, required: true } ],
    featuredImage: { type: String, required: true },
    isAvailable: { type: Boolean, default: true },
    isFeatured: { type: Boolean, default: false },
    isPublish: { type: Boolean, default: false }
  }, { timestamps: true }
);
module.exports = model('Room', roomSchema);

优化后的查询代码(使用聚合)

通过$lookup关联查询,筛选出没有冲突预订的房间:

const targetCheckIn = new Date('2019-12-25');
const targetCheckOut = new Date('2019-12-30');

const availableRooms = await Room.aggregate([
  // 第一步:筛选基础条件(可预订、已发布)
  { $match: { isAvailable: true, isPublish: true } },
  // 第二步:关联查询该房间的所有冲突预订(非取消+日期重叠)
  {
    $lookup: {
      from: 'reservations',
      let: { roomId: '$_id' },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                { $eq: ['$room', '$$roomId'] },
                { $ne: ['$status', 'cancel'] },
                { $lt: ['$checkIn', targetCheckOut] },
                { $gt: ['$checkOut', targetCheckIn] }
              ]
            }
          }
        }
      ],
      as: 'conflictingReservations'
    }
  },
  // 第三步:筛选没有冲突预订的房间
  { $match: { conflictingReservations: { $size: 0 } } }
]);

额外优化:添加索引

为了提升查询速度,可以给Reservation集合添加复合索引:

reservationSchema.index({ room: 1, status: 1, checkIn: 1, checkOut: 1 });

总结

  • 如果是小型酒店或测试项目,直接用方案一即可,无需修改现有Schema;
  • 如果是中大型生产项目,建议用方案二拆分集合,提升系统的可扩展性和查询性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:17:24