Підрахунок рядків у доктрині QueryBuilder


197

Я використовую 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 для пагинації.)


1
ви просто хочете повернути кількість результатів? ваш код не дуже зрозумілий. чому не працює getQuery ()?
jere

Щоб створити сторінку з доктрином2, перегляньте це розширення: github.com/beberlei/DoctrineExtensions
Стефан

3
@Stefan зараз є частиною ORM. docs.doctrine-project.org/en/latest/tutorials/pagination.html
Євген

Відповіді:


474

Щось на зразок:

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

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

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


Він не просив рахувати без предикатів ( bar = $bar);)
Йован Перович

4
Він прийняв вашу відповідь, тож я гадаю, все добре. У мене було враження, що він хотів лише порахувати без накладних витрат фактично витягувати рядки, які показує мій приклад. Звичайно, немає причин, коли умови не можна було б додати.
Серад

50
+1 за використання getSingleScalarResult (). використовуючи count()на $query->getResult()насправді робить запит повертає результати (що він НЕ хоче). Я думаю, що на це слід прийняти відповідь
1212 року

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

Використання вільного інтерфейсу - це інший підхід, який дуже корисний у випадку, якщо ви збираєтесь писати статичні запити. Якщо є необхідність перемикатися там, де умови, наприклад, виконуючи кожен метод самостійно, мають і свої переваги.
barbieswimcrew

3
Ви можете написати цеreturn ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult();
Barh

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, воно просто відмовиться працювати.
Паоло Штефан

це рішення створює додатковий запит, на зразок 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/isissue/6290
Wilt

6

Приклад роботи з групуванням, об'єднанням та ін.

Проблема:

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

Для цього можливим рішенням є користувальницький гідрататор, і ця дивна річ під назвою "CUSTOM OUTPUT WALKER HINT":

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
Я вважаю за краще просто написати рідний запит, ніж мати справу з цим кодом Руба Голдберга.
клавіатураMmasher

Це хороший приклад того, як хитра Symfony: Щось таке просте, як базовий щоденний підрахунок SQL, потрібно вирішувати абсолютно складними написаннями, написаними самостійно ... ух, я маю на увазі, просто вау! Ще дякую Сергію за цю відповідь!
Sliq

4

Для людей, які використовують лише доктрину DBAL, а не доктрину ORM, вони не зможуть отримати доступ до getQuery()методу, оскільки він не існує. Їм потрібно зробити щось на кшталт наступного.

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

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

0

Ви також можете отримати кількість даних за допомогою функції підрахунку.

$query = $this->dm->createQueryBuilder('AppBundle:Items')
                    ->field('isDeleted')->equals(false)
                    ->getQuery()->count();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.