Count Rows in Doctrine QueryBuilder - symfony

I'm using Doctrine's QueryBuilder to build a query, and I want to get the total count of results from the query.
$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();
I just want to run a count on this query to get the total rows, but not return the actual results. (After this count query, I'm going to further modify the query with maxResults for pagination.)

Something like:
$qb = $entityManager->createQueryBuilder();
$qb->select('count(account.id)');
$qb->from('ZaysoCoreBundle:Account','account');
$count = $qb->getQuery()->getSingleScalarResult();
Some folks feel that expressions are somehow better than just using straight DQL. One even went so far as to edit a four year old answer. I rolled his edit back. Go figure.

Here is another way to format the query:
return $repository->createQueryBuilder('u')
->select('count(u.id)')
->getQuery()
->getSingleScalarResult();

It's better to move all logic of working with database to repositores.
So in controller you write
/* you can also inject "FooRepository $repository" using autowire */
$repository = $this->getDoctrine()->getRepository(Foo::class);
$count = $repository->count();
And in Repository/FooRepository.php
public function count()
{
$qb = $repository->createQueryBuilder('t');
return $qb
->select('count(t.id)')
->getQuery()
->getSingleScalarResult();
}
It's better to move $qb = ... to separate row in case you want to make complex expressions like
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();
}
Also think about caching your query result - 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();
}
In some simple cases using EXTRA_LAZY entity relations is good
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

If you need to count a more complex query, with groupBy, having etc... You can borrow from Doctrine\ORM\Tools\Pagination\Paginator:
$paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);
$totalRows = count($paginator);

Since Doctrine 2.6 it is possible to use count() method directly from EntityRepository. For details see the link.
https://github.com/doctrine/doctrine2/blob/77e3e5c96c1beec7b28443c5b59145eeadbc0baf/lib/Doctrine/ORM/EntityRepository.php#L161

Example working with grouping, union and stuff.
Problem:
$qb = $em->createQueryBuilder()
->select('m.id', 'rm.id')
->from('Model', 'm')
->join('m.relatedModels', 'rm')
->groupBy('m.id');
For this to work possible solution is to use custom hydrator and this weird thing
called '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);

For people who are using only Doctrine DBAL and not the Doctrine ORM, they will not be able to access the getQuery() method because it doesn't exists. They need to do something like the following.
$qb = new QueryBuilder($conn);
$count = $qb->select("count(id)")->from($tableName)->execute()->fetchColumn(0);

To count items after some number of items (offset), $qb->setFirstResults() cannot be applied in this case, as it works not as a condition of query, but as an offset of query result for a range of items selected (i. e. setFirstResult cannot be used togather with COUNT at all). So to count items, which are left I simply did the following:
//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;
Anybody knows more clean way to do it?

Adding the following method to your repository should allow you to call $repo->getCourseCount() from your 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();
}

You can also get the number of data by using the count function.
$query = $this->dm->createQueryBuilder('AppBundle:Items')
->field('isDeleted')->equals(false)
->getQuery()->count();

Related

How to merge two queries in Doctrine ORM

I have table "weights", where I have numbers of weights for various cameras.
I want to sum last numbers for this cameras, therefore I am using two queries.
First query selects max ids for every camera and second selects numbers of weight for these ids.
public function maxIds($cameras)
{
return $this->createQueryBuilder("c")
->select('MAX(c.id) ')
->where('c.camera IN (:value)')
->setParameter('value', $cameras)
->groupBy('c.camera')
->getQuery()
->getArrayResult();
}
public function totalWeight($ids)
{
return $this->createQueryBuilder("c")
->select('SUM(c.number)')
->where('c.id IN (:value)')
->setParameter('value', $ids)
->getQuery()
->getOneOrNullResult();
}
These two queries work fine, but I would like to combine them into one query.
I am trying with this:
public function testWeight($cameras)
{
$qr = $this->createQueryBuilder('c')
->select('MAX(c.id) ')
->where('c.camera IN (:value)')
->setParameter('value', $cameras)
->groupBy('c.camera');
$qr->andWhere($qr->expr()->in('c.id',
$this->_em->createQueryBuilder()
->select('SUM(c.number)')
->getDQL()
));
return $qr->getQuery()
->getOneOrNullResult();
}
But I cannot success.
How can I solve this problem?
This is untested however what you are trying to do is a sub query which you can achieve in Doctrine. I also made some minor improvements to your query:
$subQuery = $this->getEntityManager()->createQueryBuilder();
$subQuery
->select('MAX(c2.id)')
->from(MyEntity::class, 'c2')
->where($subQuery->expr()->in('c2.camera', ':cameras'));
$queryBuilder = $this->createQueryBuilder('c')
$queryBuilder
->select('SUM(c.number)')
->where($queryBuilder->expr()->in('c.id', $subQuery->getDQL()))
->setParameter('cameras', $cameras);
->getQuery()
->getOneOrNullResult();

Doctrine ORM : Calculated related entity count in one shot

I've a User entity and a Product entity.
class User{
/*
* #ORM\OneToMany(targetEntity="Product", mappedBy="User")
*/
private $Products;
}
class Product{
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="Products")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $User;
}
Now I'm trying to display a html table of users, but I want to show each user's product count too.
By using following code I'm able to obtain the users objects.
$qb = $this->_em->createQueryBuilder();
$qb->select('usr')
->from('User', 'usr');
$query = $qb->getQuery();
But I don't know how to get the products count in one shot. Any help?
First, you should really create a repository class for your entities if you want to create custom queries. Then you can simply run that query by injecting the entity repository as a service wherever you need it and then running the query method.
Second, you need to return a result from a doctrine query to retrieve anything from the database. If you want to determine the count of the objects returned, simply do this:
$qb = $this->_em->createQueryBuilder();
$qb->select('usr')
->from('User', 'usr');
$query = $qb->getQuery();
$count = count($query->getResult());
A doctrine query will return an array of objects matching your query. If you just want to return a count of the matching records, try something like this:
$qb = $this->_em->createQueryBuilder();
$qb->select('count(id)')
->from('User', 'usr');
$query = $qb->getQuery();
$count = $query->getSingleScalarResult();
Or to just get a count of Product objects for that user, from within the User repository class:
$qb = $this->_em->createQueryBuilder('usr');
$qb->select('count(p.id)')
->from('usr.Products', 'p');
return $qb->getQuery()->getSingleScalarResult();

Symfony2 Doctrine2 use of partials

From reading the documentation and another stackoverflow post, I thought that if I only want to return a couple of columns data, the correct method in doctrine was to use a partial. (This is a read only query).
However the below code returns all 100 columns instead of the 3 I identified. Can someone explain why?
Thanks,
Manisha
public function showAction(Request $request)
{
if ($request->getMethod() == 'GET') {
$id = $request->get('locationid');
$kfType = $request->get('type');
$em = $this->getDoctrine()
->getManager();
$data = $em->createQueryBuilder()
->select ( array( 'partial d.{id, locationid, kfFyp}' ))
->from('DashDataBundle:Data', 'd')
->where('d.locationid = :locationid')
->setParameter('locationid', $id)
->setMaxResults(100)
->getQuery()
->getResult();
}
This query will return doctrine entities which have many fields. But then you use partial keywords this fields would be empty. Only specified fields would be filled with data.
If you doesnt want to hydrate objects you can get data in simple array if you specify it
->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY)

How to execute two repository functions in one Action

I have a problem. I want to execute this
$events1 = $em->getRepository('AfishaBundle:Event')->getEvents($this->getCurrentRegion(),'2012-11-12');
//$event_repo->clear();`
$events2 = $em->getRepository('AfishaBundle:Event')->getEvents($this->getCurrentRegion(),'2012-11-13');
foreach($events2 as $event){
die(var_dump($event->getSeanses()->toArray()));
}
But result return with first date = "2012-11-12"
when I not use $event_repo->clear();
That's ok but I have a problem with Twig rendering.
Can I execute this without clear() method ? If I can't, please comment.
For sake of completeness, this is getEvents() code:
public function getEvents($region, $date){
$qb = $this->_em->createQueryBuilder();
$qb
->select('e,s')
->from('AfishaBundle:Event', 'e')
->leftJoin('e.seanses', 's')
->andWhere('s.date = :date')
->andWhere('s.region = :region')
->setParameter('region', $region)
->setParameter('date',$date)
;
return $qb->getQuery()->getResult();
}
This is the relation between Event and EventSeans:
/**
* #ORM\OneToMany(
* targetEntity="EventSeans",
* cascade={"persist", "remove", "merge"},
* mappedBy="event"
* )
* #ORM\OrderBy({"time"= "ASC"})
*/
protected $seanses;
Finally, this is date field in EventSeans entity
/**
* #ORM\Column(name="date", type="date")
*/
protected $date;
Please, consider that it's really hard to understand what you are asking for! I understood that when you pass two different dates to the getEvents() function you always get the same result.
It seems that the code you currently posted is ok, but you are going to do is not to select the Event entities with the getEvents() function.
If you ready the query from you QueryBuilder, you are selecting the EventSeans entities such that the $region and $date are the one provided by you! But you are nor selecting the Event entities nor involving the relationship between Event and EventSeans entities.
Maybe with more details and a better explanation of your problem one could helps.
EDIT
I think you should not select both Event and EventSeanses, since you can get all the EventSeanses objects from the Event entity once you retrieve the Event from repo. But I suggest to select all the EventSeanses such that the criteria are satisfied, and then to get the associated Event object directly from the EventSeanses object. Clean and easy.
public function getEventSeanses($region, $date){
$qb = $this->_em->createQueryBuilder();
$qb
->select('s')
->from('AfishaBundle:EventSeanses', 's')
->andWhere("s.date = ':date'")
->andWhere('s.region = :region')
->setParameter('region', $region)
->setParameter('date',$date)
;
return $qb->getQuery()->getResult();
}
Notice that in SQL you should put strings (like a date) like follows 'date' and not simply date.
Then, in your controller you should do as follows:
$events = array();
$region=...
$date=....//year-month-day
$eventseansesList = $em->getRepository('AfishaBundle:EventSeanses')->getEventSeanses($region,$date);
foreach($eventseansesList as $es){
$events[] $es->getEvent(); // this function retrieve the Event associated to the EventSeanses
}
PS: You order $seanses by "time" in Event and not by "date", That's strange!

How to use wildcards in createQueryBuilder?

In my repository class i use:
public function getItemsByTag($tag)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->where('c.tags LIKE %bipolar%')
->addOrderBy('c.id');
return $qb->getQuery()
->getResult();
}
But unfortunately this doesn't work.. Anybody knows how this can work? Or do I have to build a custom query without the QueryBuilder?
Thanks!
Searching based on a single parameter:
I think it should go:
public function getItemsByTag($tag)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->where('c.tags LIKE :tag')
->addOrderBy('c.id')
->setParameter('tag', $tag);
return $qb->getQuery()->getResult();
}
But I think that it is discouraged to do a LIKE as part of a where using the query builder so you should do:
$qb = $this->createQueryBuilder('c');
$qb->select('c')
->where($qb->expr()->like('c.tags', '?1'))
->addOrderBy('c.id')
->setParameter(1, $tag);
return $qb->getQuery()->getResult();
Check out the docs for more information, there is an example of a like expression in the section entitled Helper Methods
I should also point out that I used a different convention in each example for passing a parameter into a query, the first used a named parameter :tag which is set by setParameter('tag', $value) the second is just a numbered parameter ?1, you could have just as easily have used a named parameter in the second example if you wished to as well.
Searching with an array of parameters:
You also asked about doing an array of likes. Here it is with an OR expression but if you wanted to search for all tags you could change it to an AND.
In order to make a "LIKE array" you just have to build up the expression on its own.
$qb = $this->createQueryBuilder('c');
$orExpr = $qb->expr()->orX();
for ($i = 0; $i < count($tags); $i++) {
$orExpr->add($qb->expr->like('c.tags', "?$i"));
// You may have to set params later in a loop after $orExpr has been
// added to the queryBuilder.
$qb->setParameter($i, $tags[$i]);
}
$qb->select('c')->where($orExpr)->addOrderBy('c.id');
return $qb->getQuery()->getResult();
If you don't want to substitute your query with variables but use a static string you have to put the string in apostrophes.
You have to use apostrophes instead of quotes! Otherwise the Doctrine2 Lexer will throw an Exception.
So in your case Mike you can use:
'c.tags LIKE \'%bipolar%\''
or
"c.tags like '%bipolar%'"
I don't know much about Symfony, but based on what I know about PHP and MySQL, I imagine you mean 'c.tags LIKE "%bipolar%"'. You likely need quotation marks around %bipolar%.
simply:
public function getItemsByTag($tag)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->where( $qb->expr()->like('c.tags', ':tags') )
->addOrderBy('c.id');
$qb->setParameter('tags', '%' . $tag . '%' );
return $qb->getQuery()->getResult();
}

Resources