Policz rzędy w Doctrine QueryBuilder

197

Korzystam z QueryBuilder Doctrine do zbudowania zapytania i chcę uzyskać całkowitą liczbę wyników z zapytania.

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

Chcę tylko przeliczyć na to zapytanie, aby uzyskać całkowitą liczbę wierszy, ale nie zwracam rzeczywistych wyników. (Po tym zapytaniu zliczającym zamierzam dalej zmodyfikować zapytanie za pomocą maxResults do paginacji).

Acyra
źródło
1
chcesz tylko zwrócić liczbę wyników? twój kod nie jest bardzo jasny. dlaczego getQuery () nie działa?
jere
Aby zbudować paginację za pomocą doctrine2, spójrz na to rozszerzenie: github.com/beberlei/DoctrineExtensions
Stefan
3
@Stefan jest teraz częścią ORM. docs.doctrine-project.org/en/latest/tutorials/pagination.html
Eugene

Odpowiedzi:

474

Coś jak:

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

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

Niektórzy ludzie uważają, że wyrażenia są w jakiś sposób lepsze niż zwykłe używanie DQL. Jeden posunął się nawet do edycji czteroletniej odpowiedzi. Wycofałem jego edycję. Domyśl.

Cerad
źródło
Nie poprosił o liczenie bez predykatów ( bar = $bar);)
Jovan Perovic
4
Zaakceptował twoją odpowiedź, więc chyba wszystko jest w porządku. Odniosłem wrażenie, że chciał tylko liczyć bez narzutu na faktyczne wyszukiwanie wierszy, które pokazuje mój przykład. Oczywiście nie ma powodu, dla którego nie można byłoby dodać warunków.
Cerad
50
+1 za użycie getSingleScalarResult (). użycie count()on $query->getResult()powoduje, że zapytanie zwraca wyniki (czego nie chciał). Myślę, że to powinno być zaakceptowane odpowiedź
jere
18
Najbardziej przenośnym sposobem jest robienie$qb->select($qb->expr()->count('account.id'))
webbiedave
1
czy ktoś może wyjaśnić, dlaczego muszę używać select('count(account.id)')zamiast select('count(account)')?
Stepan Yudin
51

Oto inny sposób sformatowania zapytania:

return $repository->createQueryBuilder('u')
            ->select('count(u.id)')
            ->getQuery()
            ->getSingleScalarResult();
HappyCoder
źródło
Używanie płynnego interfejsu to inne podejście, które jest bardzo pomocne w przypadku, gdy zamierzasz pisać zapytania statyczne. Jeśli istnieje potrzeba przełączania, gdzie warunki, na przykład samodzielne wykonanie każdej metody, ma również swoje zalety.
barbieswimcrew
3
Możesz to napisaćreturn ($qb = $repository->createQueryBuilder('u'))->select($qb->expr()->count('u.id'))->getQuery()->getSingleScalarResult();
Barh
25

Lepiej przenieść całą logikę pracy z bazą danych do repozytoriów.

Więc w kontrolerze piszesz

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

I w Repository/FooRepository.php

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

Lepiej jest przejść $qb = ...do osobnego wiersza na wypadek, gdybyś chciał utworzyć złożone wyrażenia takie jak

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

Pomyśl także o buforowaniu wyniku zapytania - 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();
}

W niektórych prostych przypadkach EXTRA_LAZYdobrze jest używać relacji encji
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

Luchaninov
źródło
17

Jeśli potrzebujesz policzyć bardziej złożone zapytanie, za pomocą groupBy, havingitp ... Możesz pożyczyć od Doctrine\ORM\Tools\Pagination\Paginator:

$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);
Nathan Kot
źródło
8
Przydatne, ale należy pamiętać: to rozwiązanie będzie działać w przypadku zapytań dotyczących pojedynczego podmiotu - przy złożonych instrukcjach select po prostu odmówi działania.
Paolo Stefan,
to rozwiązanie generuje dodatkowe zapytanie, SELECT COUNT(*) AS dctrn_count FROM (_ORIGINAL_SQL_) dctrn_result) dctrn_tablektóre w rzeczywistości nie jest niczym specjalnym, ale dobrze znane COUNT (*) rozwiązanie
Vladyslav Kolesov
$ paginator-> getTotalItemCount () też byłoby rozwiązaniem
cwhisperer
11

Ponieważ Doctrine 2.6możliwe jest użycie count()metody bezpośrednio z EntityRepository. Aby uzyskać szczegółowe informacje, patrz link.

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

Sławomir Kania
źródło
Tak, wygląda to na świetne rozwiązanie i działa w prostszych przypadkach (możesz przekazać kryteria filtrowania liczenia), ale nie udało mi się sprawić, aby działał dla kryteriów ze skojarzeniami (filtrowanie według skojarzeń). Zobacz powiązany post tutaj: github.com/doctrine/orm/issues/6290
Wilt
6

Przykład pracy z grupowaniem, związkami i innymi rzeczami.

Problem:

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

Aby to zadziałało, możliwym rozwiązaniem jest użycie niestandardowego nawilżacza i tej dziwnej rzeczy zwanej „WSKAZANIE NIESTANDARDOWEJ WALKERA”:

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
źródło
7
Wolę po prostu napisać natywne zapytanie niż zająć się tym kodem Rube Goldberga.
keyboardSmasher
To dobry przykład tego, jak gówniana jest Symfony: coś prostego, jak podstawowa codzienna liczba SQL, musi zostać rozwiązane za pomocą całkowicie skomplikowanych, napisanych przez siebie rzeczy ... wow, to znaczy, po prostu wow! Wciąż dziękuję Siergiejowi za tę odpowiedź!
Sliq
4

W przypadku osób, które używają tylko Doctrine DBAL, a nie Doctrine ORM, nie będą mogły uzyskać dostępu do getQuery()metody, ponieważ ona nie istnieje. Muszą zrobić coś takiego:

$qb = new QueryBuilder($conn);
$count = $qb->select("count(id)")->from($tableName)->execute()->fetchColumn(0);
Starx
źródło
4

Aby policzyć elementy po pewnej liczbie elementów (przesunięcie), w tym przypadku nie można zastosować $ qb-> setFirstResults (), ponieważ nie działa to jako warunek zapytania, ale jako przesunięcie wyniku zapytania dla wybranego zakresu elementów ( tj. setFirstResult nie może być w ogóle używany z COUNT). Aby policzyć pozostałe rzeczy, po prostu wykonałem następujące czynności:

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

Czy ktoś wie, jak to zrobić w bardziej czysty sposób?

Oleksii Zymovets
źródło
0

Dodanie następującej metody do repozytorium powinno pozwolić na wywołanie $repo->getCourseCount()z kontrolera.

/**
 * @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
źródło
0

Możesz także uzyskać liczbę danych, używając funkcji zliczania.

$query = $this->dm->createQueryBuilder('AppBundle:Items')
                    ->field('isDeleted')->equals(false)
                    ->getQuery()->count();
Abhi Das
źródło