Contar linhas no Doctrine QueryBuilder

197

Estou usando o QueryBuilder do Doctrine para criar uma consulta e quero obter a contagem total de resultados da 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();

Eu só quero executar uma contagem nesta consulta para obter o total de linhas, mas não retornar os resultados reais. (Após essa consulta de contagem, vou modificar ainda mais a consulta com maxResults para paginação.)

Acyra
fonte
1
você só quer retornar o número de resultados? seu código não é muito claro. por que o getQuery () não funciona?
Jere
Para criar paginação com doutrina2, dê uma olhada nesta extensão: github.com/beberlei/DoctrineExtensions
Stefan
3
@Stefan agora faz parte do ORM. docs.doctrine-project.org/en/latest/tutorials/pagination.html
Eugene

Respostas:

474

Algo como:

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

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

Algumas pessoas acham que as expressões são de alguma forma melhores do que apenas usar DQL direto. Um deles chegou ao ponto de editar uma resposta de quatro anos. Revirei sua edição de volta. Vai saber.

Cerad
fonte
Ele não pediu uma contagem sem predicados ( bar = $bar);)
Jovan Perovic
4
Ele aceitou sua resposta, então acho que está tudo bem. Fiquei com a impressão de que ele só queria uma contagem sem a sobrecarga de recuperar as linhas que meu exemplo mostra. Obviamente, não há razão para que as condições não possam ser adicionadas.
Cerad
50
+1 para usar getSingleScalarResult (). usar count()on $query->getResult()está realmente fazendo a consulta retornar os resultados (que é o que ele não queria). Eu acho que isso deve ser aceito resposta
jere
18
A maneira mais portátil é fazer$qb->select($qb->expr()->count('account.id'))
webbiedave
1
Alguém pode explicar por que devo usar em select('count(account.id)')vez de select('count(account)')?
Stepan Yudin
51

Aqui está outra maneira de formatar a consulta:

return $repository->createQueryBuilder('u')
            ->select('count(u.id)')
            ->getQuery()
            ->getSingleScalarResult();
HappyCoder
fonte
O uso da interface fluente é uma abordagem diferente que é muito útil caso você pretenda escrever consultas estáticas. Se houver a necessidade de alternar onde as condições, por exemplo, executar cada método por si só, também tem vantagens.
barbieswimcrew
3
Você pode escrever issoreturn ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult();
Barh
25

É melhor mover toda a lógica de trabalhar com o banco de dados para repositórios.

Então no controlador você escreve

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

E em Repository/FooRepository.php

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

É melhor mudar $qb = ...para uma linha separada, caso você queira criar expressões complexas 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();
}

Pense também no cache do resultado da sua 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();
}

Em alguns casos simples, EXTRA_LAZYé bom usar relações de entidade
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

luchaninov
fonte
17

Se você precisar contar uma consulta mais complexa, com groupBy, havingetc ... Você pode pedir emprestado de Doctrine\ORM\Tools\Pagination\Paginator:

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);
Nathan Kot
fonte
8
Útil, mas observe: esta solução funcionará para consultas em uma única entidade - com instruções de seleção complexas, apenas se recusará a trabalhar.
Paolo Stefan
esta solução produz consulta adicional, como SELECT COUNT(*) AS dctrn_count FROM (_ORIGINAL_SQL_) dctrn_result) dctrn_tableo que é realmente nada especial, mas solução COUNT bem conhecido (*)
Vladyslav Kolesov
$ paginator-> getTotalItemCount () também seria uma solução
cwhisperer
11

Como Doctrine 2.6é possível usar o count()método diretamente de EntityRepository. Para detalhes, consulte o link.

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

Sławomir Kania
fonte
Sim, parece uma ótima solução e funciona para casos mais simples (você pode passar critérios para filtrar a contagem), mas não consegui fazê-lo funcionar para critérios com associações (filtragem por associações). Veja post relacionado aqui: github.com/doctrine/orm/issues/6290
Wilt
6

Exemplo de trabalho com agrupamento, união e outras coisas.

Problema:

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

Para que isso funcione, a solução possível é usar o hidratador personalizado e essa coisa estranha chamada 'DICAS DE PERSONALIZAÇÃO DA SAÍDA 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
fonte
7
Prefiro escrever uma consulta nativa do que lidar com esse código Rube Goldberg.
keyboardSmasher
Esse é um bom exemplo de como o Symfony é uma merda: Algo simples como uma contagem básica diária de SQL precisa ser resolvido com coisas auto-escritas totalmente complicadas ... uau, quero dizer, uau! Ainda obrigado a Sergey por esta resposta!
Sliq 03/07/19
4

Para pessoas que estão usando apenas o Doctrine DBAL e não o Doctrine ORM, elas não poderão acessar o getQuery()método porque ele não existe. Eles precisam fazer algo como o seguinte.

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

Para contar itens após algum número de itens (deslocamento), $ qb-> setFirstResults () não pode ser aplicado nesse caso, pois funciona não como uma condição de consulta, mas como um deslocamento do resultado da consulta para um intervalo de itens selecionados ( ou seja, setFirstResult não pode ser usado para reunir com COUNT). Então, para contar os itens restantes, fiz o seguinte:

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

Alguém sabe uma maneira mais limpa de fazer isso?

Oleksii Zymovets
fonte
0

Adicionar o seguinte método ao seu repositório deve permitir que você chame $repo->getCourseCount()do seu Controller.

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

Você também pode obter o número de dados usando a função de contagem.

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