How to add dynamically select aliases inside a query - symfony

Context: Given the fact that the following query :
$queryBuilder = $this->createQueryBuilder("cv")
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
Will trigger 1+X distinct queries (one to get all the CV, then if u.user is used in the template, will trigger X other queries to fetch users).
If I want to optimize and to reduce those multiple unoptimized queries to 1 single query, i'll do so :
$queryBuilder = $this->createQueryBuilder("cv")
->select('cv, u')
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
This Way, i'll be able to save X queries.
Now, my problem is in my repository, I have conditional joins and I want to chain the select aliases at different places in my code.
Like (simplified example) :
$queryBuilder = $this->createQueryBuilder("cv")
->select('cv, u')
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
if ($myCondition === true) {
$queryBuilder->add('select', 'l');
$queryBuilder->join('cv.level', 'l');
}
But it seems that the add->('select') does not stack like an addWhere().
Are there any other solutions than using a custom solution like this :
$queryBuilder = $this->createQueryBuilder("cv")
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
$aliases = array('cv', 'u');
if ($myCondition === true) {
$aliases[] = 'l';
$queryBuilder->add('select', 'l');
$queryBuilder->join('cv.level', 'l');
}
$queryBuilder->select(implode(',', $aliases);
Thanks.

// Replace
$queryBuilder->add('select', 'l');
// With
$queryBuilder->addSelect('l');
And a bit of unsolicited advice. I know how much "un optimized queries" bother most people, including myself. However, consider doing some bench marks on large data sets. It's surprising how fast lazy loading is. Very little difference even with thousands of queries.

Related

Is there a correct way in Doctrine to nest 'OR' where clauses?

I have a query that pulls in some jobs if they have an end date and are not completed which produces the desired result:
real code
$this->createQueryBuilder('j')
->where('j.completed = :false OR j.completed is NULL')
->andWhere('j.has_due_date = :true')
->andWhere('j.has_due_date = :true')
->setParameters( array(
'false' => FALSE,
'true' => TRUE
) )
->orderBy('j.due_date', 'asc')
->getQuery();
result
SELECT j FROM App\Entity\Jobs j
WHERE (
j.completed = :false
OR j.completed is NULL
)
AND j.has_due_date = :true
ORDER BY j.due_date ASC
I'm wanting to follow the DQL best practice for this and feel like there is another way or writing this and only having a single WHERE clause per call on a ->where variant (andWhere(), orWhere())
In my mind it's something like this but I can't find anything out there to confirm it:
pseudo code
$query = $this->createQueryBuilder('j')
->where( $requiredClass->
->where('j.completed = :false')
->orWhere('j.completed is NULL')
->setParameter('false', FALSE)
)
->andWhere('j.has_due_date = :true')
->setParameter('true', TRUE)
->orderBy('j.due_date', 'asc')
->getQuery();
Main questions are:
Is there a way to do this the second way?
am I overcomplicating things unnecessarily?
if there is a second way to do this as mentioned, the first statement works so why should it matter?
Using the query builder should be easier and more understandable
public function someFunction()
{
$qb = $this->createQueryBuilder('j');
$orX = $qb->expr()->orX();
$andX = $qb->expr()->andX();
$orX->add($qb->expr()->eq('j.completed', $qb->expr()->literal(false)));
$orX->add($qb->expr()->isNull('j.completed'));
$andX->add($qb->expr()->eq('j.has_due_date', $qb->expr()->literal(true)));
$qb->andWhere($orX, $andX)
->orderBy('j.due_date', 'asc')
->getQuery()
->getResult();
}
As stated in the official documentation for QueryBuilder best practice - the right way is to use the helper method QueryBuilder::expr() and also the Doctrine\ORM\Query\Expr helper class. High level API methods
i.e.:
// create QueryBuilder
$qb = $this->createQueryBuilder('j');
// build conditions
$qb->where(
$qb->expr()->orX(
$qb->expr()->eq('j.completed', ':false'),
$qb->expr()->isNull('j.completed')
)
)
->andWhere(
$qb->expr()->eq('j.has_due_date', ':true')
)
->andWhere(
$qb->expr()->eq('j.has_due_date', ':true')
)
->setParameters(array(
'false' => FALSE,
'true' => TRUE
))
->orderBy('j.due_date', 'asc');
// get query
$query = $qb->getQuery();
// execute and get result
$result = $query->getResult();

Symfony 3 one-to-many, get parent with all children if one child satisfy the codition

I have these two tables
I want to get all products(with all the children) that have at least one child with log_id = 13. Let's say I have the following rows in eorder_product_config table:
The function that retrieves the products looks like this:
public function getProducts($logId){
$q = $this
->createQueryBuilder('p')
->select('p', 'pc')
->where('pc.logisticStatus = :logId')
->setParameter('logId', $logId)
->getQuery();
return $q->getResult();
}
This will get the product(id = 18) with only 2 children(id = 46,48) in the productConfigs collection and I want have all 5 children if there is at least one that has log_id = 13.
I've found a workaround using subqueries:
public function getProducts($logId){
// search for configs that have log_id = 13
$subQuery = $this->createQueryBuilder('pp')
->select('DISTINCT pp.id')
->leftJoin('pp.productConfigs', 'ppc')
->leftJoin('ppc.logisticStatus', 'pls')
->where('ppc.logisticStatus = :logId');
//->andWhere('ppc.id = p.id'); // used for EXIST query method
// main query
$q = $this->createQueryBuilder('p');
$q->select('p', 'pc');
$q->leftJoin('p.productConfigs', 'pc')
// inject subquery, check to see if current product is in
// the subquery result
$q->where($q->expr()->in('p.id', $subQuery->getDQL()));
//$q->where($q->expr()->exists($subQuery->getDQL()))
$q->setParameter('logId', $logId);
return $q->getQuery()->getResult();
}
***I've seen that using the EXIST query does't work as it should that's why I choose the IN query. But in the raw sql query they both return same results.

Using Criteria object in a query builder in Doctrine

I have 2 query builders. One for fetching all results (paginated) and one for getting the total count.
The criteria for these query will be nearly the same, so I want to use a Criteria object to just attach to the query builder afterwards.
$queryBuilder = $this->createQueryBuilder('vo');;
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq("vo.creator", $currentUser));
$criteria->where(Criteria::expr()->eq("vo.other", $something));
$queryBuilder->addCriteria($criteria);
The problem is: In the middle of a lot of ANDs I also have an OR. Just in a plain query builder it reads:
$criteria->andWhere('vo.public = :isPublic OR vo.offerID IN (:sharedOfferIDs)');
Using Laravel Eloquent, you could beautifully do that
->where('vo.creator', '=', $currentUser)
->orWhere(function ($query) {
query->where('vo.public ', '=', true)
->where('vo.offerID', 'in', [3, 4]');
})
To be even clearer, I want the result SQL to be something like:
where vo.creator = $currentUser AND (vo.public = true OR vo.offerID in [3, 4])
How can I archive this using Doctrine Criteria?
Something like this:
$array = [3, 4];
$queryBuilder = $this->createQueryBuilder('vo');
$queryBuilder->where('vo.creator = :creator')
->andWhere('vo.public = true OR vo.offerID IN (:array) ')
->setParameter('creator', $currentUser)
->setParameter('array', $array);

How to save row order which is pointed in "IN()" expression?

I have a array of entity IDs - my task is fetch that entities in the order which is pointed in the array. I have found that combination of "IN()" and "FIND_IN_SET" can solve that task.
I built a query in repository class with the help of QueryBuilder:
$qb = $this->createQueryBuilder('v');
$qb
->select('v')
->addSelect("FIND_IN_SET('v.id', '$vehiclesStr')")
->andWhere('v.id IN(:vehicles)')
->setParameter('vehicles', $vehiclesArr)
;
return $qb->getQuery()->getResult();
As you see, I use "FIND_IN_SET" function from beberlei/DoctrineExtensions. It was registered accordingly to that issue.
Suppose $vehiclesStr = '219,197,213,198'; and respectively
$vehiclesArr = [219,197,213,198];
The problem - order is not saving. I receive following result, where vehicles are ordered by ASC e.g. 197, 198, 213..:
UPDATE / built SQL by Doctrine:
SELECT
v0_.vehicle_id AS vehicle_id_0,
v0_.number AS number_1,
v0_.cargo_movers AS cargo_movers_2,
v0_.vat AS vat_3,
v0_.bargain AS bargain_4,
v0_.cargo_search_radius AS cargo_search_radius_5,
v0_.adr AS adr_6,
v0_.tir AS tir_7,
v0_.passing_cargo AS passing_cargo_8,
v0_.description AS description_9,
v0_.created_at AS created_at_10,
v0_.updated_at AS updated_at_11,
v0_.transport_service_id AS transport_service_id_12,
v0_.vehicle_photo_id AS vehicle_photo_id_13,
v0_.vehicle_driver_id AS vehicle_driver_id_14,
v0_.vehicle_type_id AS vehicle_type_id_15,
v0_.vehicle_body_size_id AS vehicle_body_size_id_16,
v0_.vehicle_full_size_id AS vehicle_full_size_id_17,
v0_.vehicle_show_to_cargo_sender_id AS vehicle_show_to_cargo_sender_id_18,
v0_.vehicle_body_equipment_id AS vehicle_body_equipment_id_19,
v0_.vehicle_loading_type_id AS vehicle_loading_type_id_20,
v0_.vehicle_price_around_town_id AS vehicle_price_around_town_id_21,
v0_.vehicle_price_out_of_town_id AS vehicle_price_out_of_town_id_22
FROM
vehicle v0_
WHERE
v0_.vehicle_id IN (?)
ORDER BY
FIND_IN_SET('v.id', '219,188') ASC
Parameters: [[219, 188]]
You're not using ORDER BY clause at all. Putting additional field in SELECT clause won't change order.WHy would it?
Try to use orderBy instead of addSelect.
$qb = $this->createQueryBuilder('v');
$qb
->select('v')
->andWhere('v.id IN(:vehicles)')
->setParameter('vehicles', $vehiclesArr)
->orderBy("FIND_IN_SET(v.id, '$vehiclesStr')", 'ASC');
;
return $qb->getQuery()->getResult();
Assuming $vehiclesStr has correct value of course.

Call to undefined method Slice in Doctrine

I have this function,
public function getWall(){
$q = $this->createQueryBuilder('f');
$q->leftJoin("f.profilo", 'p');
$q->leftJoin("p.utente", 'u');
$q->where('(f.foto_eliminata IS NULL OR f.foto_eliminata != 1)');
$q->andWhere('p.fase_registrazione = :fase');
$q->andWhere('u.locked = :false');
$q->slice(0, 20);
$q->setParameter(':fase', 100);
$q->setParameter('false', false);
$q->orderBy('f.created_at', 'desc');
$dql = $q->getQuery();
$results = $dql->execute();
return $results;
}
but I get this error,
Call to undefined method Doctrine\ORM\QueryBuilder::slice()
Ok, so, u get this error, cause QueryBuilder has no such method. But Collection has. If u want to use slice, a possible variant is:
use Doctrine\Common\Collections;
public function getWall(){
$result = $this->createQueryBuilder('f')
->leftJoin("f.profilo", 'p')
->leftJoin("p.utente", 'u')
->where('(f.foto_eliminata IS NULL OR f.foto_eliminata != 1)')
->andWhere('p.fase_registrazione = :fase')
->andWhere('u.locked = :false')
->setParameter('fase', 100)
->setParameter('false', false)
->orderBy('f.created_at', 'desc')
->getQuery()
->getResult();
// $result typed as array
return new Collections\ArrayCollection($result))->slice(0,20); // convert array to collection, then slice
}
By the way, it is not a good idea to 'limit' result of the query in a such way.
U can use setMaxResults(20), and not to select all objects at all.
About lazy collections (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html): after selecting result objects, u can take some object from result collection: $r = $result[0] after that:
$portfilos = $r->getPortfolio(); // returns for example Collection with some objects;
// its Lazy, without SQL query!
$portfolios->slice(0, 20); // queries first 20 potfolios
To use slice is a rather good idea, if u have lots of objects in some relation.
p.s. sry, mb I didn't recognized your problem, but tried :)
EDITED
fixed errors in code.

Resources