在Doctrine QueryBuilder中计算行数

我正在使用Doctrine的QueryBuilder来build立一个查询,我想从查询中得到结果的总数。

$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进一步修改查询分页。)

就像是:

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

编辑

有些人认为expression式比直接使用DQL更好。 甚至还编辑了一个四岁的答案。 我把他的编辑回来了。 去搞清楚。

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

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

将所有与数据库一起工作的逻辑移动到存储库更好。

所以在控制器你写

 $repository = $this->getDoctrine()->getRepository('FooBundle:Foo'); $count = $repository->count(); 

FooBundle/Repository/FooRepository.php

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

如果你想制作复杂的expression式,最好移动$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(); } 

也考虑caching您的查询结果 – 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

如果你需要计算一个更复杂的查询,用groupBy ,等等…你可以借用Doctrine\ORM\Tools\Pagination\Paginator

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

使用分组,联合和东西的例子。

问题:

  $qb = $em->createQueryBuilder() ->select('m.id', 'rm.id') ->from('Model', 'm') ->join('m.relatedModels', 'rm') ->groupBy('m.id'); 

为了这个工作可能的解决scheme是使用自定义hydrator和这个奇怪的事情称为“自定义输出步行者提示”:

 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); 

为了对一些项目(偏移量)之后的项目进行计数,在这种情况下$ 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; 

有人知道更干净的方法来做到这一点?

对于只使用Doctrine DBAL而不使用Doctrine ORM的人,他们将不能访问getQuery()方法,因为它不存在。 他们需要做如下的事情。

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

将以下方法添加到您的存储库应允许您从Controller中调用$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(); }