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


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

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.


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

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


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

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


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

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

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?


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

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();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.