Contar filas en Doctrine QueryBuilder

197

Estoy usando Doctrine's QueryBuilder para construir una consulta, y quiero obtener el recuento total de resultados de la consulta.

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

Solo quiero ejecutar un recuento en esta consulta para obtener las filas totales, pero no devolver los resultados reales. (Después de esta consulta de conteo, voy a modificar aún más la consulta con maxResults para paginación).

Acyra
fuente
1
solo quieres devolver el número de resultados? Su código no es muy claro. ¿Por qué no funciona getQuery ()?
jere
Para construir paginación con doctrine2, eche un vistazo a esta extensión: github.com/beberlei/DoctrineExtensions
Stefan
3
@Stefan ahora es parte de ORM. docs.doctrine-project.org/en/latest/tutorials/pagination.html
Eugene

Respuestas:

474

Algo como:

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

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

Algunas personas sienten que las expresiones son de alguna manera mejores que simplemente usar DQL directo. Incluso se llegó a editar una respuesta de cuatro años. Hice rodar su edición hacia atrás. Imagínate.

Cerad
fuente
No pidió un conteo sin predicados ( bar = $bar);)
Jovan Perovic
44
Él aceptó tu respuesta, así que supongo que todo está bien. Tenía la impresión de que solo quería un recuento sin la sobrecarga de recuperar realmente las filas que muestra mi ejemplo. Por supuesto, no hay ninguna razón por la cual no se puedan agregar condiciones.
Cerad
50
+1 por usar getSingleScalarResult (). usar count()on $query->getResult()está haciendo que la consulta devuelva los resultados (que es lo que no quería). Creo que esto debería ser aceptado respuesta
jere
18
La forma más portátil es hacerlo$qb->select($qb->expr()->count('account.id'))
webbiedave
1
¿Alguien puede explicar por qué debo usar en select('count(account.id)')lugar de select('count(account)')?
Stepan Yudin
51

Aquí hay otra forma de formatear la consulta:

return $repository->createQueryBuilder('u')
            ->select('count(u.id)')
            ->getQuery()
            ->getSingleScalarResult();
HappyCoder
fuente
El uso de la interfaz fluida es un enfoque diferente que es muy útil en caso de que tenga la intención de escribir consultas estáticas. Si existe la necesidad de alternar donde las condiciones, por ejemplo, la ejecución de cada método por sí mismo, también tiene sus ventajas.
barbieswimcrew
3
Puedes escribir estoreturn ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult();
Barh
25

Es mejor mover toda la lógica de trabajar con bases de datos a repositores.

Entonces en el controlador escribes

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

Y en Repository/FooRepository.php

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

Es mejor moverse $qb = ...a una fila separada en caso de que desee crear expresiones complejas como

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

También piense en almacenar en caché el resultado de su consulta: 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();
}

En algunos casos simples, usar las EXTRA_LAZYrelaciones entre entidades es bueno
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

luchaninov
fuente
17

Si necesita contar una consulta más compleja, con groupBy, havingetc ... Puede tomar prestado de Doctrine\ORM\Tools\Pagination\Paginator:

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);
Nathan Kot
fuente
8
Útil, pero tenga en cuenta: esta solución funcionará para consultas en una sola entidad, con declaraciones de selección complejas, simplemente se negará a funcionar.
Paolo Stefan
esta solución produce una consulta adicional como la SELECT COUNT(*) AS dctrn_count FROM (_ORIGINAL_SQL_) dctrn_result) dctrn_tableque en realidad no es nada especial, sino una conocida solución COUNT (*)
Vladyslav Kolesov
$ paginator-> getTotalItemCount () también sería una solución
cwhisperer
11

Dado Doctrine 2.6que es posible utilizar el count()método directamente desdeEntityRepository . Para más detalles ver el enlace.

https://github.com/doctrine/doctrine2/blob/77e3e5c96c1beec7b28443c5b59145eeadbc0baf/lib/Doctrine/ORM/EntityRepository.php#L161

Sławomir Kania
fuente
Sí, parece una gran solución y funciona para casos más simples (puede pasar criterios para filtrar el recuento), pero no logré que funcione para criterios con asociaciones (filtrado por asociaciones). Ver publicación relacionada aquí: github.com/doctrine/orm/issues/6290
Wilt
6

Ejemplo de trabajo con agrupación, unión y demás.

Problema:

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

Para que esto funcione, la solución posible es usar un hidratador personalizado y esta cosa extraña llamada 'CONSEJO DEL CAMINANTE DE SALIDA PERSONALIZADA':

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);
Sergey Poskachey
fuente
77
Prefiero escribir una consulta nativa que tratar con ese código Rube Goldberg.
keyboardSmasher
Ese es un buen ejemplo de lo horrible que es Symfony: algo simple como un conteo básico de SQL todos los días debe resolverse con cosas totalmente complicadas escritas a sí mismas ... wow, quiero decir, ¡simplemente wow! Aún así gracias a Sergey por esta respuesta!
Sliq
4

Para las personas que usan solo Doctrine DBAL y no Doctrine ORM, no podrán acceder al getQuery()método porque no existe. Necesitan hacer algo como lo siguiente.

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

Para contar elementos después de cierto número de elementos (desplazamiento), $ qb-> setFirstResults () no se puede aplicar en este caso, ya que no funciona como una condición de consulta, sino como un desplazamiento del resultado de la consulta para un rango de elementos seleccionados ( es decir, setFirstResult no se puede usar junto con COUNT). Entonces, para contar los elementos que quedan, simplemente hice lo siguiente:

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

¿Alguien sabe más forma limpia de hacerlo?

Oleksii Zymovets
fuente
0

Agregar el siguiente método a su repositorio debería permitirle llamar $repo->getCourseCount()desde su Controlador.

/**
 * @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();
}
crmpicco
fuente
0

También puede obtener el número de datos utilizando la función de conteo.

$query = $this->dm->createQueryBuilder('AppBundle:Items')
                    ->field('isDeleted')->equals(false)
                    ->getQuery()->count();
Abhi Das
fuente