Doctrine DQL - nested subqueries in WHERE clause throw Syntax Error - php

I'm trying to query data from one table based on the SUMed and COALESCEd result of two associated tables.
The query works fine when executed as native SQL, unfortunately my application is restricted to only using Doctrine's DQL on a QueryBuilder object here.
This is how I build my QueryBuilder:
$resUnitQuery = $queryBuilder->getEntityManager()->createQueryBuilder();
$resUnitQuery->select('COALESCE(SUM(pc.residentialUnits), 0)')
->from(PropertyConnection::class, 'pc')
->where("$rootAlias.id = pc.contract");
$comUnitQuery = $queryBuilder->getEntityManager()->createQueryBuilder();
$comUnitQuery->select('COALESCE(SUM(pwa.residential_units), 0)')
->from(PropertyWithoutAddress::class, 'pwa')
->where("$rootAlias.id = pwa.contract");
$queryBuilder = $this->getEntityManager(Contract::class)->createQueryBuilder();
$queryBuilder
->select('id')
->from(Contract::class, 'o')
->andWhere(
':residentialUnits <= ' .
sprintf(
'((%s) + (%s))',
$resUnitQuery->getQuery()->getDQL(),
$comUnitQuery->getQuery()->getDQL()
)
)
->setParameter('residentialUnits', $params['rangeFrom']);
The generated DQL code is as follows:
SELECT id
FROM App\Entity\Contract o
WHERE :residentialUnits <= ((SELECT COALESCE(SUM(pc.residentialUnits), 0) FROM App\Entity\PropertyConnection pc WHERE o.id = pc.contract) + (SELECT COALESCE(SUM(pwa.residential_units), 0) FROM App\Entity\PropertyWithoutAddress pwa WHERE o.id = pwa.contract))
This results in the following error:
[Syntax Error] line 0, col 66: Error: Expected Literal, got 'SELECT'
I found an old Doctrine GitHub issue stating that double parenthetis can cause issues, which is why I skipped them in my WHERE clause and replaced
((%s) + (%s))
with
(%s) + (%s)
The surrounding parenthesis are required for native SQL to work, and it also results in an error from Doctrine:
[Syntax Error] line 0, col 174: Error: Expected end of string, got '+'
May I be running into restrictions of Doctrine's DQL here?
According to the docs, both SUM and COALESCE are supported, as well as nested queries.
Thanks a lot in advance,
Rene

Correct, the DBAL does not support arithmetic operations on subqueries. Additional the DBAL does not support joined subqueries, JOIN (SELECT FROM...) ON without using a native query.
Another issue is the WHERE statement of your subqueries, being dependant on the root query, will cause your root query to perform a full table scan. Executing each of the subqueries per-row, unless a criteria is added to the root query (WHERE o.id ...).
As the subquery SUM values are dependant on the root query id. You can rewrite the query to use a subquery as a hidden column and a HAVING clause, to filter the id result set from the added column results.
$em = $queryBuilder->getEntityManager();
$expr = $em->getExpressionBuilder();
$qbPC = $em->createQueryBuilder()
->select('COALESCE(SUM(pc.residentialUnits), 0)')
->from(App\Entity\PropertyConnection::class, 'pc')
->where($expr->eq('pc.contract', "$rootAlias.id"));
$qbPWA = $em->createQueryBuilder()
->select('COALESCE(SUM(pwa.residential_units), 0)')
->from(App\Entity\PropertyWithoutAddress::class, 'pwa')
->where($expr->eq('pwa.contract', "$rootAlias.id"));
$qb = $this->getEntityManager(Contract::class)
->createQueryBuilder()
->select('o.id')
->from(App\Entity\Contract::class, 'o')
->addSelect('(' . $qbPC->getDQL() . ') AS HIDDEN pc_ru')
->addSelect('(' . $qbPWA->getDQL() . ') AS HIDDEN pwa_ru')
->having($expr->lte(':v', 'pc_ru + pwa_ru'))
->setParameter('v', $params['rangeFrom']);
dump($qb->getDQL());
Resulting DQL
SELECT
o.id,
(SELECT
COALESCE(SUM(pc.residentialUnits), 0)
FROM App\Entity\PropertyConnection pc
WHERE pc.contract = o.id
) AS HIDDEN pc_ru,
(SELECT
COALESCE(SUM(pwa.residential_units), 0)
FROM App\Entity\PropertyWithoutAddress pwa
WHERE pwa.contract = o.id
) AS HIDDEN pwa_ru
FROM App\Entity\Contract o
HAVING :v <= pc_ru + pwa_ru

Related

How to use multiple condition in leftjoin with raw query in laravel5

I need to join two table to fetch data with multiple condition on leftjoin but
i am getting this error. Not enough arguments for the on clause I am using laravel5.2. How to use raw query with leftjoin multiple condition.
$activityDetail = DB::table('table1 as UA')
->selectRaw('SUM(UA.total_calory) as total_calory,
SUM(UA.flight_descend) as flight_descend,date('.$tz_start_date.') as start_date')
->leftjoin('table2 as LC',function($join) use($tz_lccreated_date,$dateRange){
$join->on('LC.user_id_fk','=','UA.user_id_fk');
$join->on('LC.is_active','=',DB::raw('1'));
$join->on(' date('.DB::raw($tz_lccreated_date).') '.DB::raw($dateRange));
})
->whereRaw(' date('.$tz_start_date.') '.$dateRange)
->where('UA.user_id_fk','=',base64_decode($params['uid']))
->groupBy(DB::raw('date('.$tz_start_date.')'))
->get();
Raw query is
select SUM(UA.total_calory) as total_calory,
SUM(UA.flight_descend) as flight_descend,
date(CONVERT_TZ(UA.start_date,"+00:00","+05:30")) as start_date
from `table1` as `UA`
left join `table2` as `LC`
on `LC`.`user_id_fk` = `UA`.`user_id_fk` and `LC`.`is_active` = 1
and `date(CONVERT_TZ(LC`.`created_date,"+00:00","+05:30"))` = `current_date`
where date(CONVERT_TZ(UA.start_date,"+00:00","+05:30")) = current_date
and `UA`.`user_id_fk` = 411
group by date(CONVERT_TZ(UA.start_date,"+00:00","+05:30"))
I have resolved my problem, after a lots of analysis, i am not getting any kind of solution of raw query in join clause then i am using subquery and resolve my problem.
$mannualLoggedQuery = 'IFNULL((select sum(calory) as cal from table2 as LC where LC.user_id_fk = "'.base64_decode($params['uid']).'" and date('.$tz_lccreated_date.')'.$dateRange.'),0)';
$activityDetail = DB::table('table1 as UA')->selectRaw('SUM(UA.total_calory) + '.$mannualLoggedQuery.' as total_calory,
date('.$tz_start_date.') as start_date')
->where('UA.user_id_fk','=',base64_decode($params['uid']))
->whereRaw('date('.$tz_start_date.') '.$dateRange)
->groupBy(DB::raw('date('.$tz_start_date.')'))
->get();

Conditionnally join table on Doctrine2

I seeked for an answer but anything in my particular case.
I need to join contintionnally table with Doctrine 2, depends on value of a field I have to join on two different foreign keys, here my code :
$qb = $this->entityManager->createQueryBuilder();
$qb ->select('s')
->from('AppBundle:MyTable', 's')
->join('s.firstJoin', 'o')
->join('s.secondJoin', 'd')
->join('AppBundle:joinedView', 'view', Join::WITH,
"(CASE WHEN (d.secondJoinFK = 3)
THEN view.did = d.secondJoinFK
WHEN (d.secondJoinFK = 2)
THEN view.dvid = d.secondJoinFK END)")
->addSelect('d')
->where('s.endDate IS NULL');
However, with this request, Symfony tells me : [Syntax Error] line 0, col 203: Error: Expected Doctrine\ORM\Query\Lexer::T_ELSE, got '='
Moreover, I cannot use native query because I use PagerFanta for rendering template, and PagerFanta needs to have a ORM\Query on input and not ORM\NativeQuery or other.
Unfortunately, I do not have choice and must implement this request with these prerequisites.
Thanks by advance for your help,
It is not possible to do the something like conditional join. You always need to join both tables and specify different join condition.
You can do something like this:
->join('AppBundle:joinedView', 'view1', Join::WITH,
"view1.did = 3")
->join('AppBundle:joinedView', 'view2', Join::WITH,
"view2.dvid = 2")
but honestly I am not sure what are you trying to achieve. Joining tables without a condition which uses some joining column seems a bit pointless.

How to do a select query in LeftJoin querybuilder

How can I convert below query to Doctrine:
SELECT restaurants.restaurant_name ,
restaurants.restaurant_id,
j.LASTPRICE
FROM restaurants
LEFT JOIN
(
SELECT f.food_id AS fid,
f.restaurants_restaurant_id AS rid,
Max(f.food_last_price) AS LASTPRICE
FROM foods AS f
LEFT JOIN restaurants AS r
ON r.restaurant_id = f.restaurants_restaurant_id
WHERE f.food_last_price IS NOT NULL
GROUP BY r.restaurant_id) j
ON restaurants.restaurant_id = j.rid
Here is my code:
$qb = $this->_em->createQueryBuilder();
$qb2 = $this->_em->createQueryBuilder();
$getMaxPercentage = $qb2
->select(
'MAX (Food.foodLastPrice) AS LASTPRICE ',
'Food.foodId AS fId',
'Food.restaurantsRestaurantId AS rID'
)
->from($this->entityClass,'Restaurant')
->innerJoin('Restaurant.foods','Food')
->where('Food.foodLastPrice IS NOT NULL')
->groupBy('Restaurant.restaurantId')
->getDQL();
$restaurantList = $qb
->select('Restaurants.restaurantName, Restaurants.restaurantId , j.LASTPRICE')
->from($this->entityClass,'Restaurants')
->leftJoin($getMaxPercentage,'j','WITH','Restaurants.restaurantId = j.rID')
->getQuery()->execute();
dd($restaurantList);
I give an error :
SELECT Restaurants.restaurantName,': Error: Class 'SELECT' is not defined.
I've already known I could set sub queries in main query, Although in this case I does not want to use sub query in Where expression. Any suggestion for using select in LeftJoin in doctrine?
EDITED : I've tried to use DQL in my query:
$query = $this->_em->createQuery(
'
SELECT Restaurants.restaurantName , Restaurants.restaurantId
FROM App\\Restaurant AS Restaurants
LEFT JOIN (
SELECT f.foodId AS fid,
f.restaurantsRestaurantId AS rid,
Max(f.foodLastPrice) AS LASTPRICE
FROM App\\Food AS f
LEFT JOIN App\\Restaurant AS r
WITH r.restaurantId = f.restaurantsRestaurantId
GROUP BY r.restaurantId) AS J
ON Restaurants.restaurantId = j.rid
');
But I gave an another error :
[Semantical Error] Error: Class '(' is not defined.
Is it possible to use select in left join in Doctrine?
EDITED 2 : I read a similar question and I've decided to write in another way :
$qb = $this->_em->createQueryBuilder();
$qb2 = $this->_em->createQueryBuilder();
$subSelect = $qb2
->select(
array(
'Food.foodLastPrice AS LASTPRICE ',
'Food.foodId AS fId',
'Food.restaurantsRestaurantId AS rId')
)
->from($this->entityClass,'Restaurant')
->innerJoin('Restaurant.foods','Food')
->where('Food.foodLastPrice IS NOT NULL')
->groupBy('Restaurant.restaurantId')
->getQuery()->getSQL();
$restaurantList =
$qb->select(
'Restaurant1'
)
->from($this->entityClass, 'Restaurant1')
->leftJoin('Restaurant1',sprintf('(%s)',$subSelect),'internalQuery','Restaurant1.restaurantId = internalQuery.rId')
->getQuery()->getSQL();
dd($restaurantList);
Again, I got an error:
near 'Restaurant1 (SELECT': Error: Class 'Restaurant1' is not defined.
What you're trying to do is impossible :
https://github.com/doctrine/doctrine2/issues/3542 (november 2013, but doctrine concept didn't change since and doctrinebot closed this on 7 Dec 2015)
DQL is about querying objects. Supporting subselects in the FROM
clause means that the DQL parser is not able to build the result set
mapping anymore (as the fields returned by the subquery may not match
the object anymore). This is why it cannot be supported (supporting it
only for the case you run the query without the hydration is a no-go
IMO as it would mean that the query parsing needs to be dependant of
the execution mode).
In your case, the best solution is probably to run a SQL query instead
(as you are getting a scalar, you don't need the ORM hydration anyway)
You can find workarounds like raw sql, or rethinking your query.

Symfony Doctrine Group By Query

I have below sql query running fine,
SELECT completed_by, count(*) AS Total
FROM tasks
WHERE completed_by is not null AND status = 1
GROUP BY completed_by
;
Em am doing it with doctrine query builder, but not working returning an error.
$parameters = array(
'status' => 1,
);
$qb = $repository->createQueryBuilder('log');
$query = $qb
->select(' log.completedBy, COUNT(log) AS Total')
->where('log.Status = :status')
->groupBy('log.completedBy')
->setParameters($parameters)
->getQuery();
and getting below error;
[Semantical Error] line 0, col 21 near 'completedBy,': Error: Invalid
PathExpression. Must be a StateFieldPathExpression.
I know this answer can be late, but I struggled with the exact same problem, and did not find any answer on the internet, and I believe a lot of people will struggle in this same issue.
I'm assuming your "completedBy" refers to another entity.
So, inside your repository, you can write:
$query = $this->createQueryBuilder("log")
->select("completer.id, count(completer)")
->join("log.completedBy", "completer")
->where('log.Status = :status')
->groupBy("completer")
->setParameters($parameters)
->getQuery();
This will compile to something like:
SELECT completer.id, count(completer) FROM "YOUR LOG CLASS" log INNER JOIN log.completedBy completer WHERE log.Status=:status GROUP BY completer
Now, You can do another query to get those 'completers', by their ids.
This is wrong: COUNT(log) AS Total. It should be something like COUNT(log.log) AS Total.
When you want to select a column who is a fk for another table (entity), use the IDENTITY function instead of the column name only.
Example: In your case
$parameters = array(
'status' => 1,
);
$qb = $repository->createQueryBuilder('log');
$query = $qb
->select('IDENTITY(log.completedBy), COUNT(log.something) AS Total')
->where('log.Status = :status')
->groupBy('log.completedBy')
->setParameters($parameters)
->getQuery();

Conditions in SUM function

I have a User entity with one-to-many relationship with Certificate entity (one user might have multiple certificates).
A Certificate has a startDate and endDate which determine their period of validity. I need filter all the users, that have expired certificates and have no active certificates.
An active certificate - c.startDate <= :today AND c.endDate > :today
An expired certificate - c.endDate <= :today.
I now how to do it in plain SQL:
SELECT
u.user_id
FROM `User` u JOIN `Certificate` c ON c.user_id = u.user_id
GROUP BY u.user_id
HAVING SUM(c.end_date > :today) = 0
Now I'm trying to port the SQL query to Doctrine QueryBuilder syntax:
$qb = $this->createQueryBuilder();
$qb
->from('SomeNS:User', 'u')
->select('u')
->join('u.certificates', 'c')
->groupBy('u.id')
->having('SUM(c.endDate > :today) = 0')
->setParameter('today', $today);
But an error occurs:
Error: Expected Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS, got '<'
It looks like the conditions in the SUM function aren't understood by Doctrine.
Please help to figure out how one can use conditions in SUM function or point me out what I do wrong.
Here is a concrete answer using the expr() method of queryBuilder:
---
->having(
$qb->expr()->andx( // this is the "AND" in between the sums
$qb->expr()->lte( // this is the "<="
...(your SUM here, after you fix the logic),
0
),
$qb->expr()->eq( // this is the "="
...(your SUM here, after you fix the logic),
0
)
)
)
Once you get those "SUM" conditions right, you put them in the placeholders and it should work.

Categories