How to do a select query in LeftJoin querybuilder - php

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.

Related

Doctrine DQL - nested subqueries in WHERE clause throw Syntax Error

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

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

How to use "distinct on" with doctrine?

I try to use "distinct on" with doctrine but I get the following error:
Error: Expected known function, got 'on'
class Report extends EntityRepository
{
public function findForList() {
$queryBuilder = $this->createQueryBuilder('r');
$queryBuilder->select('distinct on (r.parentId)')
->orderBy('r.parentId')
->orderBy('r.date', 'DESC');
return $queryBuilder->getQuery()->getResult();
}
}
How could I implement the following query?
select distinct on (r.parent_id)
r.parent_id,
r.id,
r.name
from frontend.report r
order by r.parent_id, r.date desc;
Apparently it doesn't seem possible to do this with the query builder. I tried to rewrite my query in different ways:
select * from frontend.report r
where
r.id in (
select distinct
(select r3.id from frontend.report r3
where r3.parent_id = r.parent_id
order by r3.date desc limit 1) AS id
from frontend.report r2);
But doctrine doesn't support LIMIT:
public function findForList() {
$qb3 = $this->_em->createQueryBuilder();
$qb3->select('r3.id')
->from('MyBundle:frontend\report', 'r3')
->where('r3.parentId = r2.parentId')
->orderBy('r3.date', 'DESC')
//->setMaxResults(1)
;
$qb2 = $this->_em->createQueryBuilder();
$qb2->select('(' . $qb3->getDql() . ' LIMIT 1)')
->from('MyBundle:frontend\report', 'r2')
->distinct(); // groupBy('r2.parentId')
$queryBuilder = $this->createQueryBuilder('r');
$queryBuilder->where(
$queryBuilder->expr()->in('rlt.id', $qb2->getDql())
);
return $queryBuilder->getQuery()->getResult();
}
I think the only solution is to use native SQL queries.
This response is for someone that yet looking for the solution in similar cases.
If you need a sample "DISTINCT" you can use:
$queryBuinder->select('parentId')
->distinct('parentId');
but if you want a "DISTINCT ON" you should use Native SQL instead of Query Builder, check out the manual here: Doctrine Native SQL
Just remove the on word :
$queryBuilder->select('distinct r.parentId')
->orderBy('r.parentId')
->orderBy('r.date', 'DESC');

Doctrine query "where notIn" subquery issue

In Symfony2, I have a many:many relationship between users and roles.
I am trying to get a list of all the users which are not linked to the ROLE_SUPER_ADMIN role.
Before migrating to Symfony2/Doctrine, I had achieved this with a simple NOT IN sql query, but for the life of me I can't achieve the same effect with doctrine.
Here is what I am trying:
$em = $this->getDoctrine()->getManager();
$qb = $em->createQueryBuilder();
$qb2 = $qb;
$dql = $qb->select('sa.id')
->from('AcmeAdminBundle:User', 'sa')
->leftJoin('sa.roles', 'r')
->andWhere('r.role = :role')
->getDQL();
$result = $qb2->select('u')
->from('AcmeAdminBundle:User', 'u')
->where($qb2->expr()->notIn('u.id', $dql))
->setParameter('role', 'ROLE_SUPER_ADMIN')
$users = $result->getQuery()->getResult();
But this is the error:
[Semantical Error] line 0, col 140 near 'sa LEFT JOIN':
Error: 'sa' is already defined.
And this is the output:
SELECT u
FROM AcmeAdminBundle:User sa
LEFT JOIN sa.roles r, AcmeAdminBundle:User u
WHERE u.id NOT IN (
SELECT sa.id
FROM AcmeAdminBundle:User sa
LEFT JOIN sa.roles r
WHERE r.role = :role
)
No idea why it is outputting like that as it should not be performing LEFT JOIN two times, my suspicion is that it has something to do with having two QueryBuilder instances, but could be something else entirely.
You need the MEMBER OF or in your case NOT MEMBER OF option.
$qb->select('sa.id')
->from('AcmeAdminBundle:User', 'sa')
->where(":Role NOT MEMBER OF sa.roles")
->setParameter("Role", <<ROLE_ID_OR_ROLE_ENTITY>>);
I didn't test this code, but it should give you some idea.
Full documentation can be found at http://docs.doctrine-project.org/en/latest/reference/dql-doctrine-query-language.html

Doctrine 1.2: Can't reproduce query with Doctrine_Query

I have this SQL query:
select *
from tblapplicant AS a
WHERE a.napplicantid
not in (
select napplicantid
from tblcontract
where dstart BETWEEN '2011-10-27' AND '2012-01-26'
OR dend BETWEEN '2011-10-27' AND '2012-01-26')
And I want to build this query in Doctrine 1.2:
$Query = Doctrine_Query::create()
->select('a')
->from('tblapplicant a')
->innerJoin('a.tblintermediair i')
->where('i.nintermediairid = ? ', $intermediairid)
->addWhere('a.napplicantid NOT IN (select c.napplicantid from tblcontract c WHERE c.dstart BETWEEN ? AND ? OR c.dend BETWEEN ? AND ?)', array($this->tbljobavailable->getFirst()->dday, $this->tbljobavailable->getLast()->dday, $this->tbljobavailable->getFirst()->dday, $this->tbljobavailable->getLast()->dday));
but somehow it keeps complaining:
Couldn't find class c
Any ideas?
Just had this issue a few days ago.
One of the possible solutions is adding a one to one relation for a tblapplicant table itself. I did not like this one, so just created additional query to get ids for exclusion. In your case that would be like this:
$notIn = Doctrine_Query::create()->(put your subselect query here)->execute(array(), Doctrine_Core::HYDRATE_SINGLE_SCALAR);
$Query = Doctrine_Query::create()
->select('a')
->from('tblapplicant a')
->innerJoin('a.tblintermediair i')
->where('i.nintermediairid = ? ', $intermediairid)
->whereNotIn('a.napplicantid', $notIn);

Categories