Compter les lignes dans Doctrine QueryBuilder

198

J'utilise QueryBuilder de Doctrine pour construire une requête, et je veux obtenir le nombre total de résultats de la requête.

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

Je veux simplement exécuter un décompte sur cette requête pour obtenir le nombre total de lignes, mais pas renvoyer les résultats réels. (Après cette requête de comptage, je vais modifier davantage la requête avec maxResults pour la pagination.)

Acyra
la source
1
vous souhaitez simplement renvoyer le nombre de résultats? votre code n'est pas très clair. pourquoi getQuery () ne fonctionne-t-il pas?
Jere
Pour construire la pagination avec doctrine2, regardez cette extension: github.com/beberlei/DoctrineExtensions
Stefan
3
@Stefan fait maintenant partie de l'ORM. docs.doctrine-project.org/en/latest/tutorials/pagination.html
Eugene

Réponses:

475

Quelque chose comme:

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

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

Certaines personnes pensent que les expressions sont en quelque sorte meilleures que l'utilisation d'un DQL simple. On est même allé jusqu'à modifier une réponse vieille de quatre ans. J'ai annulé son montage. Allez comprendre.

Cerad
la source
Il n'a pas demandé un décompte sans prédicats ( bar = $bar);)
Jovan Perovic
4
Il a accepté votre réponse, donc je suppose que tout va bien. J'avais l'impression qu'il ne voulait qu'un décompte sans la surcharge de récupérer réellement les lignes que mon exemple montre. Il n'y a bien sûr aucune raison pour laquelle les conditions n'ont pas pu être ajoutées.
Cerad
50
+1 pour l'utilisation de getSingleScalarResult (). utiliser count()on $query->getResult()fait en fait que la requête retourne les résultats (ce qu'il ne voulait pas ). je pense que cela devrait être accepté réponse
jere
18
Le moyen le plus portable est de le faire$qb->select($qb->expr()->count('account.id'))
webbiedave
1
quelqu'un peut-il expliquer pourquoi je dois utiliser à la select('count(account.id)')place select('count(account)')?
Stepan Yudin
51

Voici une autre façon de formater la requête:

return $repository->createQueryBuilder('u')
            ->select('count(u.id)')
            ->getQuery()
            ->getSingleScalarResult();
HappyCoder
la source
L'utilisation de l'interface fluide est une approche différente qui est très utile si vous avez l'intention d'écrire des requêtes statiques. S'il est nécessaire de basculer là où les conditions, par exemple l'exécution de chaque méthode seule, ont également leurs avantages.
barbieswimcrew
3
Vous pouvez écrire cecireturn ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult();
Barh
25

Il est préférable de déplacer toute la logique de travail avec la base de données vers les référentiels.

Donc, dans le contrôleur, vous écrivez

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

Et en Repository/FooRepository.php

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

Il est préférable de passer $qb = ...à une ligne distincte au cas où vous voudriez créer des expressions complexes comme

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

Pensez également à mettre en cache le résultat de votre requête - 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();
}

Dans certains cas simples, utiliser des EXTRA_LAZYrelations d'entité est une bonne
chose http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

luchaninov
la source
17

Si vous devez compter une requête plus complexe, avec groupBy, havingetc ... Vous pouvez emprunter à Doctrine\ORM\Tools\Pagination\Paginator:

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);
Nathan Kot
la source
8
Utile, mais veuillez noter: cette solution fonctionnera pour les requêtes sur une seule entité - avec des instructions select complexes, elle refusera simplement de fonctionner.
Paolo Stefan
cette solution produit une requête supplémentaire comme SELECT COUNT(*) AS dctrn_count FROM (_ORIGINAL_SQL_) dctrn_result) dctrn_tablece qui n'est en fait rien de spécial mais une solution COUNT (*) bien connue
Vladyslav Kolesov
$ paginator-> getTotalItemCount () serait aussi une solution
cwhisperer
11

Puisqu'il Doctrine 2.6est possible d'utiliser la count()méthode directement depuis EntityRepository. Pour plus de détails, voir le lien.

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

Sławomir Kania
la source
Oui, cela ressemble à une excellente solution et fonctionne pour des cas plus simples (vous pouvez passer des critères pour filtrer le comptage), mais je n'ai pas réussi à le faire fonctionner pour des critères avec des associations (filtrage par associations). Voir le post connexe ici: github.com/doctrine/orm/issues/6290
Flétrissement le
6

Exemple de travail avec groupement, union et autres.

Problème:

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

Pour que cela fonctionne, la solution possible consiste à utiliser un hydrateur personnalisé et cette chose étrange appelée «CONSEIL DE MARCHE DE SORTIE PERSONNALISÉE»:

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
la source
7
Je préfère simplement écrire une requête native que de traiter ce code Rube Goldberg.
keyboardSmasher
C'est un bon exemple de la façon dont Symfony est merdique: quelque chose de simple comme un décompte SQL de base quotidien doit être résolu avec des trucs auto-écrits totalement compliqués ... wow, je veux dire, wow! Merci encore à Sergey pour cette réponse!
Sliq
4

Pour les personnes qui utilisent uniquement Doctrine DBAL et non pas Doctrine ORM, elles ne pourront pas accéder à la getQuery()méthode car elle n'existe pas. Ils doivent faire quelque chose comme ce qui suit.

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

Pour compter les éléments après un certain nombre d'éléments (décalage), $ qb-> setFirstResults () ne peut pas être appliqué dans ce cas, car il ne fonctionne pas comme condition de requête, mais comme décalage du résultat de la requête pour une plage d'éléments sélectionnés ( c'est-à-dire que setFirstResult ne peut pas du tout être utilisé avec COUNT). Donc, pour compter les éléments qui restent, j'ai simplement fait ce qui suit:

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

Quelqu'un sait comment le faire de façon plus propre?

Oleksii Zymovets
la source
0

L'ajout de la méthode suivante à votre référentiel devrait vous permettre d'appeler $repo->getCourseCount()depuis votre contrôleur.

/**
 * @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
la source
0

Vous pouvez également obtenir le nombre de données en utilisant la fonction de comptage.

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