Количество строк в Doctrine QueryBuilder

198

Я использую QueryBuilder Doctrine для создания запроса, и я хочу получить общее количество результатов из запроса.

$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 для разбивки на страницы.)

Acyra
источник
1
Вы просто хотите вернуть количество результатов? Ваш код не очень понятен. почему не работает getQuery ()?
Джер
Для построения нумерации страниц с помощью doctrine2 взгляните на это расширение: github.com/beberlei/DoctrineExtensions
Stefan
3
@Stefan теперь это часть ORM. docs.doctrine-project.org/en/latest/tutorials/pagination.html
Евгений

Ответы:

475

Что-то вроде:

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

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

Некоторые люди считают, что выражения как-то лучше, чем просто использование DQL. Один даже зашел так далеко, что отредактировал четырехлетний ответ. Я откатил его правку обратно. Пойди разберись.

Cerad
источник
Он не просил счет без предикатов ( bar = $bar);)
Йован Перович
4
Он принял ваш ответ, так что, думаю, все хорошо. У меня сложилось впечатление, что ему нужен только счет без дополнительных затрат на фактическое извлечение строк, которые показывает мой пример. Конечно, нет причин, по которым условия не могут быть добавлены.
Cerad
50
+1 за использование getSingleScalarResult (). использование count()on $query->getResult()фактически заставляет запрос возвращать результаты (чего он не хотел). Я думаю, что это должен быть принят ответ
Джер
18
Самый переносимый способ сделать это$qb->select($qb->expr()->count('account.id'))
webbiedave
1
Кто-нибудь может объяснить, почему я должен использовать select('count(account.id)')вместо select('count(account)')?
Степан Юдин
51

Вот еще один способ отформатировать запрос:

return $repository->createQueryBuilder('u')
            ->select('count(u.id)')
            ->getQuery()
            ->getSingleScalarResult();
HappyCoder
источник
Использование свободного интерфейса - это другой подход, который очень полезен, если вы собираетесь писать статические запросы. Если есть необходимость переключения, где условия, например, выполнение каждого метода самостоятельно, также имеют свои преимущества.
barbieswimcrew
3
Вы можете написать этоreturn ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult();
Барх
25

Лучше перенести всю логику работы с базой данных на репозитории.

Так в контроллере пишешь

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

И в Repository/FooRepository.php

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

Лучше перейти $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();
}

Также подумайте о кэшировании результатов запроса - 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

Лучаниновыми
источник
17

Если вам нужно посчитать более сложный запрос с помощью groupBy, и havingт.д ... Вы можете позаимствовать у Doctrine\ORM\Tools\Pagination\Paginator:

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);
Натан Кот
источник
8
Полезно, но, пожалуйста, обратите внимание: это решение будет работать для запросов к одному объекту - со сложными операторами выбора, оно просто откажется работать.
Паоло Стефан
это решение создает дополнительный запрос, подобный SELECT COUNT(*) AS dctrn_count FROM (_ORIGINAL_SQL_) dctrn_result) dctrn_tableкоторому на самом деле ничего особенного, но хорошо известное решение COUNT (*)
Владислав Колесов
$ paginator-> getTotalItemCount () тоже будет решением
cwhisperer
11

Так Doctrine 2.6как можно использовать count()метод непосредственно из EntityRepository. Подробности смотрите по ссылке.

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

Славомир Каня
источник
Да, это выглядит как отличное решение и работает для более простых случаев (вы можете пройти критерии фильтрации отсчетов), но мне не удалось заставить его работать для критериев с ассоциациями (фильтрация по ассоциациям). Смотрите похожие сообщения здесь: github.com/doctrine/orm/issues/6290
Wilt
6

Пример работы с группировкой, объединением и прочим.

Проблема:

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

Для того, чтобы это работало, возможное решение состоит в том, чтобы использовать собственный гидратор и эту странную вещь, называемую «ПОЛЬЗОВАТЕЛЬСКИЙ ВЫХОДНОЙ СОВЕТ»:

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
Я предпочел бы просто написать собственный запрос, чем иметь дело с этим кодом Rube Goldberg.
клавиатурыSmasher
Это хороший пример того, как дерьмовый Symfony: что-то простое, например, ежедневный подсчет SQL, нужно решить с помощью совершенно сложных самописных вещей ... вау, я имею в виду, просто вау! Еще спасибо Сергею за этот ответ!
Sliq
4

Для людей, которые используют только Doctrine DBAL, а не Doctrine ORM, они не смогут получить доступ к getQuery()методу, потому что он не существует. Им нужно сделать что-то вроде следующего.

$qb = new QueryBuilder($conn);
$count = $qb->select("count(id)")->from($tableName)->execute()->fetchColumn(0);
Starx
источник
4

Для подсчета элементов после некоторого количества элементов (смещение) в этом случае нельзя применить $ 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;

Кто-нибудь знает более чистый способ сделать это?

Алексей Зимовец
источник
0

Добавление следующего метода в ваш репозиторий должно позволить вам звонить $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();
}
crmpicco
источник
0

Вы также можете получить количество данных с помощью функции подсчета.

$query = $this->dm->createQueryBuilder('AppBundle:Items')
                    ->field('isDeleted')->equals(false)
                    ->getQuery()->count();
Абхи Дас
источник