15

我想用至少 2 个左连接对复杂请求进行分页,但是我正在使用的分页包 (KnpPaginationBundle) 无法告诉 Doctrine 如何计算结果(分页过程需要),并继续拥有这个例外。

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

4 回答 4

25

对于任何寻找这个答案的人,有一个很好的解决方案: 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 分页器使用它。

于 2013-07-26T14:09:00.363 回答
14

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

假设您有这样的实体:

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

重要的是它与另一个实体具有一对多的关系,并且无论出于何种原因,您都希望能够通过 QueryBuilder 加入该关系(例如,您可能希望在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();

这将引发异常:Cannot count query which selects two FROM components, cannot make distinction

要解决此问题,请重写查询,以便 QueryBuilder 仅具有一个“来自”组件——正如异常所指示的那样——通过移动内联连接。像这样:

  $qb = $this->createQueryBuilder();

  $query = $qb
    ->select('u')
    ->add('from', '\YourBundle\ORM\Model\User u LEFT JOIN u.registrations r')
    ->groupBy('u.id')
    ->having($qb->expr()->gte($qb->expr()->count('r'), '1')
    ->getQuery();

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

希望这对某人有所帮助,关于这个问题的内容不多。您可以在此处查看@halfer 的评论,了解有关内部如何处理此问题的更多信息。

于 2013-05-20T21:52:33.320 回答
3

更新doctrine/dbal到版本2.5为我解决了这个问题。

于 2015-06-26T19:00:37.287 回答
0

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

因此,关键是将您的结果集保留在一个组件中,这将允许生成和计算标量结果。

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

下面的示例显示了一对一的映射。我没有尝试过更复杂的映射,但我的成功表明它应该是可能的。注册实体有一个名为 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,所以我希望这将在任何地方工作。

于 2014-11-06T10:46:01.063 回答