学说:与左连接的分页

时间:2012-10-08 15:51:08

标签: symfony doctrine-orm pagination

我想用至少2个左连接对复杂请求进行分页,但我正在使用的分页包(KnpPaginationBundle)无法告诉Doctrine如何计算结果(which is needed for the pagination process),并继续使用这个例外。

Cannot count query which selects two FROM components, cannot make distinction

以下是使用Doctrine QueryBuilder构建的示例请求。

public function findGroupsByUser(User $user, $listFilter, $getQuery = false, $order = 'ASC')
{
    $query = $this->createQueryBuilder('r')
        ->select('r as main,g')
        ->select('r as main,g, count(gg) as members')
        ->leftjoin('r.group', 'g')
        ->innerjoin('MyBundle:GroupMemberRel', 'gg', 'WITH', 'r.group = gg.group')
        ->addGroupBy('g.groupId')
        ->add('orderBy', 'g.name ' . $order);
   if ($getQuery == true) {
        return $query;
    }

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

然后我将此请求提供给knp_paginator服务,然后我得到了异常

    $groupQuery = $this->em->getRepository('MyBundle:GroupMemberRel')->findGroupsByUser($user, $listFilter, true);
    $paginator = $this->container->get('knp_paginator');
    /* @var $groups Knp\Component\Pager\Pagination\PaginationInterface */
    $groups = $paginator->paginate(
        $groupQuery, $this->container->get('request')->query->get('page', 1), 10 /* limit per page */
    );

关于如何对复杂请求进行分页的任何想法,我很确定这个用例很常见,不希望在分页后保留我的结果。

4 个答案:

答案 0 :(得分:22)

对于那些正在寻找答案的人来说,有一个很好的解决方案: https://github.com/KnpLabs/KnpPaginatorBundle/blob/master/Resources/doc/manual_counting.md

$paginator = new Paginator;

// determine the count of the entire result set
$count = $entityManager
    ->createQuery('SELECT COUNT(c) FROM Entity\CompositeKey c')
    ->getSingleScalarResult();

// create a query to be used with the paginator, and specify a hint
$query = $entityManager
     ->createQuery('SELECT c FROM Entity\CompositeKey c')
     ->setHint(
        'knp_paginator.count', 
        $count
    );

// paginate, and set "disctinct" option to false
$pagination = $paginator->paginate(
    $query, 
    1, 
    10, 
    array(
        'distinct' => false,
    )
);

基本上,你在做什么,是你在创造自己的'计数'查询并指示knp paginator使用它。

答案 1 :(得分:14)

原始问题中的实体很难理解,但我遇到了同样的问题而且它确实是可以解决的。

假设您有这样的实体:

  class User
  {
    // ...
    /**
     * @ORM\OneToMany(targetEntity="Registration", mappedBy="user")
     */
    private $registrations;
    // ...
  }

重要的是它与另一个实体有一对多的关系,并且你希望能够通过QueryBuilder以任何理由加入它(例如,你可能想要添加一个{{1您的查询的子句只选择那些具有一个或多个其他实体的实体。)

您的原始代码可能如下所示:

HAVING

这将抛出异常: $qb = $this->createQueryBuilder(); $query = $qb ->select('u') ->add('from', '\YourBundle\ORM\Model\User u') ->leftJoin('\YourBundle\ORM\Model\Registration', 'r', 'WITH', 'u.id = r.user') ->groupBy('u.id') ->having($qb->expr()->gte($qb->expr()->count('u.registrations'), '1') ->getQuery();

要解决此问题,请重写查询,以便QueryBuilder只有一个“from”组件 - 正如异常所示 - 通过内联移动连接。像这样:

Cannot count query which selects two FROM components, cannot make distinction

使用Doctrine的Paginator类可以正常工作,不需要其他捆绑包。还要注意连接的简写语法;使用Doctrine你没有看到明确指定连接实体(尽管你可以),因为映射已经知道它是什么。

希望这可以帮助别人,在这个问题上没有太多。您可以通过查看@halfer's comment here了解有关内部处理方式的更多信息。

答案 2 :(得分:3)

doctrine/dbal更新为版本2.5为我修复此问题。

答案 3 :(得分:0)

如果您不按照上述答案中提供的方式使用常规映射实体,则连接将在结果集中创建其他组件。这意味着该集合无法计算,因为它不是标量结果。

所以关键是将结果集保存到一个组件,这样就可以生成和计算标量结果。

您可以在运行时将常规字段转换为实体映射。这允许您编写只返回一个组件的查询,即使您随机加入其他实体也是如此。 “其他实体”成为主要实体的子项,而不是其他根实体或结果的额外组成部分。

下面的示例显示了一对一的映射。我没有尝试过更复杂的映射,但是我的成功表明它应该是可能的。 Registration实体有一个名为user的字段,其中包含一个用户Id,但它不是一个映射实体,只是一个普通字段。

(这已从一个工作示例进行了修改,但未按原样进行测试 - 因此视为伪代码)

    $queryBuilder // The query builder already has the other entities added.
        ->innerJoin('r.user', 'user')
    ;


    $mapping['fieldName'] = 'user';
    $mapping['targetEntity'] = '\YourBundle\ORM\Model\User';
    $mapping['sourceEntity'] = '\YourBundle\ORM\Model\Registration';
    $mapping['sourceToTargetKeyColumns'] = array('user' => 'id');
    $mapping['targetToSourceKeyColumns'] = array('id' => 'user');
    $mapping['fetch'] = 2;
    $mapping['joinColumns'] = array(
        array(
            'name' => 'user',
            'unique' => false,
            'nullable' => false,
            'onDelete' => null,
            'columnDefinition' => null,
            'referencedColumnName' => 'id',
        )
    );
    $mapping['mappedBy'] = 'user';
    $mapping['inversedBy'] = null; // User doesn't have to link to registrations
    $mapping['orphanRemoval'] = false;
    $mapping['isOwningSide'] = true;
    $mapping['type'] = ClassMetadataInfo::MANY_TO_ONE;

    $vm = $this->em->getClassMetadata('YourBundle:Registration');

    $vm->associationMappings["user"] = $mapping;

注意:我使用的是PagerFanta而不是KNP - 但问题在于Doctrine所以我希望这可以在任何地方使用。