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

Doctrine maxResult关联查询问题:分页结果数量不符

解决Doctrine关联查询中maxResults统计主表记录数的问题

问题描述

在使用Doctrine执行包含内连接的查询时,设置setMaxResults()后,分页逻辑会基于关联后的总记录数计算,而非主表prediction_summary的记录数。例如主表有200条记录,每条关联4条prediction记录,设置maxResults=100时,实际仅返回25条主表记录,无法达到获取100条主表记录的预期。

问题根源

Doctrine的setMaxResults()直接映射到SQL的LIMIT子句,而内连接会让主表每条记录对应多条子表记录,导致最终结果集行数远大于主表行数,分页时自然会截断主表记录数量。

解决方案

方法一:先查询主表ID再关联查询(推荐)

通过子查询先获取符合条件的主表ID并完成分页,再基于这些ID关联子表数据,确保分页逻辑完全基于主表记录数。

修改后的Repository代码:

public function findByForIndex(
        ?int $client = null,
        ?int $article = null,
        ?int $year = null,
        int $page = 0,
        int $type = 0,
    ): mixed {
        // 第一步:查询符合条件的主表ID并分页
        $idQueryBuilder = $this->createQueryBuilder('ps')
            ->select('ps.id')
            ->where('ps.type = 0')
            ->orderBy('ps.client')
            ->addOrderBy('ps.article')
            ->addOrderBy('ROW_NUMBER() OVER (PARTITION BY ps.client, ps.article ORDER BY ps.orderYear)')
            ->addOrderBy('ps.orderYear')
            ->setMaxResults(self::PAGE_LIMIT)
            ->setFirstResult($page > 1 ? $page * self::PAGE_LIMIT : 0);

        // 添加过滤条件(修正原代码中别名错误:pc改为ps)
        if (!is_null($client)) {
            $idQueryBuilder->andWhere('ps.client = :clientID')->setParameter('clientID', $client);
        }

        if (!is_null($article)) {
            $idQueryBuilder->andWhere('ps.article = :articleID')->setParameter('articleID', $article);
        }

        if (1 === $type) {
            $idQueryBuilder->andWhere('ps.recurring > 0');
        } elseif (2 === $type) {
            $idQueryBuilder->andWhere('ps.incidental > 0');
        }

        $ids = $idQueryBuilder->getQuery()->getSingleColumnResult();

        if (empty($ids)) {
            return [];
        }

        // 第二步:通过ID关联子表查询完整数据
        $queryBuilder = $this->createQueryBuilder('ps')
            ->select('ps', 'p')
            ->join('ps.predictions', 'p')
            ->where('ps.id IN (:ids)')
            ->setParameter('ids', $ids)
            ->orderBy('ps.client')
            ->addOrderBy('ps.article')
            ->addOrderBy('ROW_NUMBER() OVER (PARTITION BY ps.client, ps.article ORDER BY ps.orderYear)')
            ->addOrderBy('ps.orderYear');

        return $queryBuilder->getQuery()->getResult();
    }

方法二:使用DISTINCT关键字(简单场景适用)

如果查询逻辑允许,可在select()中添加DISTINCT关键字,让Doctrine先对主表记录去重,再应用分页。注意:该方法在复杂排序或多关联场景下可能存在性能问题,且需确保排序字段均来自主表。

修改示例:

$queryBuilder = $this->createQueryBuilder('ps');
$queryBuilder
    ->select('DISTINCT ps','p') // 添加DISTINCT关键字
    ->join('ps.predictions','p')
    // 其余查询逻辑保持不变

额外注意

原代码中存在别名错误:条件判断中的pc应为ps(如pc.client实际是ps.client),需修正否则会导致SQL语法错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 10:42:37