使用TypeORM的leftJoinAndMapMany与orderBy时出现分页异常问题
解决TypeORM leftJoinAndMapMany加排序后分页丢失用户的问题
问题根源
使用leftJoinAndMapMany时,SQL会生成用户与Timelog的关联结果集——每个用户的每条Timelog对应一行数据。分页(take/skip)是作用在关联后的行集合上,而非用户实体的数量。添加排序后,若某用户的所有关联行都不在当前分页的行范围内,就会出现“用户丢失”的假象。
按timelog.logHour排序时用户完整是巧合:该字段值重复度高,同一用户的Timelog行被集中排序,刚好落在当前分页的行区间内。
解决方案
方案一:先分页用户,再批量加载关联数据(推荐)
核心思路是先保证分页对象是用户实体,再为分页后的用户批量获取Timelog,彻底避免行分页导致的用户丢失问题:
// 1. 分页查询用户主数据,仅保留关联所需字段 const pageSize = 10; const page = 1; const users = await this.createQueryBuilder('user') .select(['user.id', 'user.code']) .take(pageSize) .skip((page - 1) * pageSize) .getMany(); // 2. 批量获取当前页用户的所有Timelog const userCodes = users.map(user => user.code); const timelogs = await getRepository(Timelog) .createQueryBuilder('timelog') .where('timelog.userCode IN (:...userCodes)', { userCodes }) .orderBy('timelog.checkDate', 'ASC', 'NULLS LAST') .getMany(); // 3. 将Timelog映射到对应用户 users.forEach(user => { user.timelogs = timelogs.filter(t => t.userCode === user.code); }); return users;
方案二:通过Group By/Distinct强制分页基于用户
若必须用单查询实现,可通过group by或distinct确保分页逻辑基于用户唯一标识,同时兼容排序需求:
const query = this.createQueryBuilder('user') .leftJoinAndMapMany( 'user.timelogs', Timelog, 'timelog', 'user.code = timelog.userCode', ) .select([ 'user.id', 'user.code', // 必须包含用于group by的用户唯一字段 'timelog.id', 'timelog.projectCode', 'timelog.logHour', 'timelog.checkDate', ]) .groupBy('user.id, user.code, timelog.id') // 需根据数据库语法调整,包含所有非聚合字段 .orderBy('timelog.checkDate', 'ASC', 'NULLS LAST') .distinct(true) .take(pageSize) .skip((page - 1) * pageSize); const users = await query.getMany();
注意:不同数据库对
group by的语法要求不同(如PostgreSQL需明确列出所有非聚合字段),且该方案在Timelog数据量大时性能不如方案一。
内容的提问来源于stack exchange,提问作者Đoàn Đức Bảo
相关产品推荐
相关产品推荐

