【问题标题】:Doctrine : Pagination with left Joins教义:左连接分页
【发布时间】:2012-09-28 21:43:57
【问题描述】:

我想用至少 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 */
    );

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

【问题讨论】:

  • Pagerfanta 的理论 ORM 适配器应该能够处理奇怪的查询。 github.com/whiteoctober/Pagerfanta
  • 这个查询并不奇怪,我相信有一种方法可以简单地使用 knp 分页器,也许我应该更深入地了解一下 knp 分页器
  • 我正面临这个问题..有什么解决办法吗?
  • 那个特定的错误信息来自Doctrine\ORM\Tools\Pagination\CountWalker

标签: symfony doctrine-orm pagination


【解决方案1】:

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

【讨论】:

  • 伙计们,注意 array('distinct' => false)... 花了我很多时间
【解决方案2】:

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

假设你有这样一个实体:

  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's comment here 了解更多关于内部如何处理此问题的信息。

【讨论】:

  • 仅供参考@futureal 我尝试了你的建议,但它不起作用。 gist.github.com/TrkiSF2/16d64a8aec846f4053a5无法调查更多需要改变方法:(
  • 听起来您遇到的问题与加入无关,而是与HAVING 的使用有关,这种使用类型的分页器不支持。从提供的代码 sn-p 中,不清楚其意图是什么,但也许您正试图只获取具有 1 个或多个登录的用户?如果是这样,只需使用->where('lh.user IS NOT NULL') 代替HAVING,它应该可以工作。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-18
      • 2015-06-07
      • 1970-01-01
      • 2014-09-10
      • 1970-01-01
      • 2012-03-06
      • 2012-08-11
      相关资源
      最近更新 更多