基于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在房间数量少、预订记录不多的场景下完全够用,但如果你的酒店业务规模较大(比如数百间房,每间房有大量预订记录),嵌套数组会导致:
- 房间文档体积过大,影响查询性能
- 预订记录的增删改操作会锁定整个房间文档,并发效率低
优化后的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
相关产品推荐
相关产品推荐

