Mongoose虚拟关联查询:排除无匹配关联数据的文档
这个问题确实戳中了Mongoose populate的一个小局限——它没法在查询主文档的时候直接过滤掉没有关联子文档的结果,只能事后处理。但既然你的查询调用频率这么高,客户端过滤肯定不是最优解,我们可以把过滤逻辑放到数据库层面来做,有两种靠谱的方案:
方案一:用MongoDB聚合管道一步到位
聚合是MongoDB原生的强大工具,能在数据库内部完成关联、过滤、转换等操作,完全不需要客户端再处理。这里我们可以用$lookup来关联联赛和赛事,同时在关联阶段就过滤掉时间不符合的赛事,最后再剔除掉没有匹配赛事的联赛:
(async () => { const target_time = 1568851200000; const leagues = await Leagues.aggregate([ // 关联fixtures集合,同时过滤时间戳符合要求的赛事 { $lookup: { from: 'fixtures', // 注意这里是集合名,和你定义模型时的第二个参数一致 localField: 'id', foreignField: 'league_id', let: { targetTime: target_time }, // 子管道:只保留时间戳>=目标值的赛事 pipeline: [ { $match: { $expr: { $gte: ['$timestamp', '$$targetTime'] } } } ], as: 'fixtures' } }, // 过滤掉fixtures数组为空的联赛 { $match: { 'fixtures.0': { $exists: true } } } ]); console.log(leagues); })();
这种方式的优势是一次数据库请求完成所有操作,返回的结果直接就是你需要的,没有多余数据。另外记得给fixtures集合加个复合索引来加速查询:
FixtureSchema.index({ league_id: 1, timestamp: 1 });
这个索引能让MongoDB快速找到对应联赛下符合时间条件的赛事,大幅提升关联效率。
方案二:反向查询+精准匹配
如果觉得聚合管道写起来有点复杂,也可以换个思路:先找到所有符合时间条件的赛事对应的联赛ID,再用这些ID去查询联赛并关联赛事。这样返回的联赛天然就是有有效赛事的:
(async () => { const target_time = 1568851200000; // 第一步:获取所有有有效赛事的联赛ID,自动去重 const validLeagueIds = await Fixtures.distinct('league_id', { timestamp: { $gte: target_time } }); // 第二步:只查询这些联赛,并关联符合条件的赛事 const leagues = await Leagues.find({ id: { $in: validLeagueIds } }) .populate({ path: 'fixtures', match: { timestamp: { $gte: target_time } } }) .lean(); console.log(leagues); })();
这个方案分两次数据库请求,但逻辑更直观,而且如果有效联赛的数量远小于总联赛数,查询效率也很高。同样,给fixtures的timestamp和league_id加索引也能提升第一步distinct查询的速度。
为什么不推荐客户端过滤?
你提到的leagues.filter(item => item.fixtures.length > 0)虽然简单,但在高频率调用下,每次都要在内存中遍历所有返回的联赛对象,当数据量大的时候,这部分开销会积少成多,不如直接让数据库返回精准结果来得高效。
内容的提问来源于stack exchange,提问作者decho
相关产品推荐
相关产品推荐

