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


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

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.


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

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


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

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


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

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

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?


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

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();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.