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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 19:15:51