Doctrine QueryBuilder计算行数

237
我正在使用Doctrine的QueryBuilder来构建查询,并且想要获取查询结果的总行数。
$repository = $em->getRepository('FooBundle:Foo');

$qb = $repository->createQueryBuilder('n')
        ->where('n.bar = :bar')
        ->setParameter('bar', $bar);

$query = $qb->getQuery();

//this doesn't work
$totalrows = $query->getResult()->count();

我只是想在此查询中执行计数,以获取总行数,但不返回实际结果。(在这个计数查询之后,我将使用maxResults进一步修改查询以进行分页。)

1
你只想返回结果数量吗?你的代码不是很清晰。为什么 getQuery() 不起作用? - jere
1
构建Doctrine2分页,可以查看此扩展:https://github.com/beberlei/DoctrineExtensions - Stefan
3
@Stefan,现在它是ORM的一部分。请参阅http://docs.doctrine-project.org/en/latest/tutorials/pagination.html - Eugene
11个回答

552

类似于:

$qb = $entityManager->createQueryBuilder();
$qb->select('count(account.id)');
$qb->from('ZaysoCoreBundle:Account','account');

$count = $qb->getQuery()->getSingleScalarResult();

有些人认为使用表达式比直接使用DQL更好。甚至有人修改了四年前的答案。我将他的修改还原了。你自己想吧。


1
他没有要求不带谓词的计数(bar = $bar) ;) - Jovan Perovic
5
他接受了你的答案,所以我想一切都好了。我原来认为他只是想要一个计数,而不需要实际检索行,这也是我的例子展示的内容。当然,加入条件语句也是可以的。 - Cerad
55
使用getSingleScalarResult()方法加一分。在$query->getResult()上使用count()实际上让查询返回结果(这正是他不想要的)。我认为这应该是被接受的答案。 - jere
28
最方便的方法是使用 $qb->select($qb->expr()->count('account.id')) - webbiedave
2
有人能解释一下为什么我必须使用 select('count(account.id)') 而不是 select('count(account)') 吗? - Stepan Yudin
@Sliq - 其中一个原因是实体管理器没有from方法,所以它不起作用。工作代码(无论多么丑陋)通常被认为优于非工作代码。实际上,您可以使用$em->findAll('entity')->count(),但这涉及一些额外的开销,因为所有实体记录都将被加载。顺便说一句,Symfony并不需要Doctrine,因此可以自由使用自己的数据库访问库。 - Cerad

66

这里有另一种格式化查询的方法:

return $repository->createQueryBuilder('u')
            ->select('count(u.id)')
            ->getQuery()
            ->getSingleScalarResult();

使用流畅接口是一种不同的方法,对于编写静态查询非常有帮助。例如,如果需要切换条件,则单独执行每个方法也具有其优点。 - barbieswimcrew
3
你可以将此代码翻译为:return ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult(); - Maxim Mandrik

35

最好将与数据库交互的所有逻辑移动到存储库中。

因此在控制器中,您可以编写以下代码:

/* you can also inject "FooRepository $repository" using autowire */
$repository = $this->getDoctrine()->getRepository(Foo::class);
$count = $repository->count();

Repository/FooRepository.php

public function count()
{
    $qb = $repository->createQueryBuilder('t');
    return $qb
        ->select('count(t.id)')
        ->getQuery()
        ->getSingleScalarResult();
}

如果您想要制作复杂的表达式,最好将$qb = ...移动到单独的行中。

public function count()
{
    $qb = $repository->createQueryBuilder('t');
    return $qb
        ->select('count(t.id)')
        ->where($qb->expr()->isNotNull('t.fieldName'))
        ->andWhere($qb->expr()->orX(
            $qb->expr()->in('t.fieldName2', 0),
            $qb->expr()->isNull('t.fieldName2')
        ))
        ->getQuery()
        ->getSingleScalarResult();
}

同时考虑缓存查询结果 - http://symfony.com/doc/current/reference/configuration/doctrine.html#caching-drivers

public function count()
{
    $qb = $repository->createQueryBuilder('t');
    return $qb
        ->select('count(t.id)')
        ->getQuery()
        ->useQueryCache(true)
        ->useResultCache(true, 3600)
        ->getSingleScalarResult();
}

在某些简单的情况下,使用EXTRA_LAZY实体关系是很好的选择。
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html


22

如果您需要计算更复杂的查询,包括groupByhaving等...,您可以借鉴Doctrine\ORM\Tools\Pagination\Paginator的方法:

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);

8
有用,但请注意:此解决方案适用于针对单个实体的查询-对于复杂的选择语句,它将拒绝工作。 - Paolo Stefan
这个解决方案会产生额外的查询,如下所示:SELECT COUNT(*) AS dctrn_count FROM (_ORIGINAL_SQL_) dctrn_result) dctrn_table。实际上,这并不是什么特别的东西,而是众所周知的COUNT(*)解决方案。 - Vladyslav Kolesov
$paginator->getTotalItemCount() 也是一个解决方案。 - cwhisperer

17

是的,它看起来是一个很好的解决方案,并且适用于更简单的情况(您可以传递用于过滤计数的条件),但我无法使其适用于具有关联的条件(通过关联进行过滤)。请参见此处的相关帖子:https://github.com/doctrine/orm/issues/6290 - Wilt

6
使用分组、联合和其他操作的示例。
问题:
 $qb = $em->createQueryBuilder()
     ->select('m.id', 'rm.id')
     ->from('Model', 'm')
     ->join('m.relatedModels', 'rm')
     ->groupBy('m.id');

为了使其工作,可能的解决方案是使用自定义的水合器和这个奇怪的东西叫做“CUSTOM OUTPUT WALKER HINT”:
class CountHydrator extends AbstractHydrator
{
    const NAME = 'count_hydrator';
    const FIELD = 'count';

    /**
     * {@inheritDoc}
     */
    protected function hydrateAllData()
    {
        return (int)$this->_stmt->fetchColumn(0);
    }
}
class CountSqlWalker extends SqlWalker
{
    /**
     * {@inheritDoc}
     */
    public function walkSelectStatement(AST\SelectStatement $AST)
    {
        return sprintf("SELECT COUNT(*) AS %s FROM (%s) AS t", CountHydrator::FIELD, parent::walkSelectStatement($AST));
    }
}

$doctrineConfig->addCustomHydrationMode(CountHydrator::NAME, CountHydrator::class);
// $qb from example above
$countQuery = clone $qb->getQuery();
// Doctrine bug ? Doesn't make a deep copy... (as of "doctrine/orm": "2.4.6")
$countQuery->setParameters($this->getQuery()->getParameters());
// set custom 'hint' stuff
$countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountSqlWalker::class);

$count = $countQuery->getResult(CountHydrator::NAME);

8
我宁愿撰写一个本地查询,也不想处理那个复杂的、像鲁伯·戈尔德机器一样的代码。 - keyboardSmasher
这是Symfony有多糟糕的一个很好的例子:像基本的日常SQL计数这样简单的东西需要用完全复杂的自编写代码来解决...哇,我的意思是,真的很糟糕!还是要感谢Sergey提供的答案! - Sliq

5

如果要在某些项目(偏移量)之后计算项目的数量,不能使用$qb->setFirstResults(),因为它不是查询条件,而是选择的一组项目的查询结果的偏移量(即setFirstResult不能与COUNT一起使用)。因此,为了计算剩余项目的数量,我只需执行以下操作:

   //in repository class:
   $count = $qb->select('count(p.id)')
      ->from('Products', 'p')
      ->getQuery()
      ->getSingleScalarResult();

    return $count;

    //in controller class:
    $count = $this->em->getRepository('RepositoryBundle')->...

    return $count-$offset;

有没有更加简洁的方法来做这件事?

5

对于只使用Doctrine DBAL而不是Doctrine ORM的人,他们将无法访问getQuery()方法,因为它不存在。他们需要执行以下操作。

$qb = new QueryBuilder($conn);
$count = $qb->select("count(id)")->from($tableName)->execute()->fetchColumn(0);

0
在Symfony 6中,这可能是最简单的方法:

$this->manager()->getRepository(SomeEntity::class)->count(['field' => $value])


0
将以下方法添加到您的存储库中,应该允许您从控制器调用$repo->getCourseCount()
/**
 * @return array
 */
public function getCourseCount()
{
    $qb = $this->getEntityManager()->createQueryBuilder();

    $qb
        ->select('count(course.id)')
        ->from('CRMPicco\Component\Course\Model\Course', 'course')
    ;

    $query = $qb->getQuery();

    return $query->getSingleScalarResult();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接