Join a subquery with Doctrine DQL - symfony

Using Doctrine in Symfony2, I need to recover each items and for each of them, the latest timestamp of their report.
So I would like to execute a query using DQL, which would be like this in SQL:
SELECT * from `item` i
LEFT JOIN `kit` k ON k.`id` = i.`kit_id`
LEFT JOIN
(SELECT e.`item_id`, MAX(e.`dateCreation`)
FROM `entete_rapport` e
GROUP BY e.`item_id`) latest ON latest.`item_id` = i.`id`
I am not able to have the same with DQL. I guess I have to separate the subquery et the main one, with something like this:
$subSelect->select('e AS ItemId, MAX(e.dateCreation) AS latest')
->from('CATUParkBundle:EnteteRapport', 'e')
->groupBy('e.item');
$qb->select('i')
->from('CATUParkBundle:Item', 'i')
->leftJoin('i.kit', 'k')
->leftJoin('CATUParkBundle:EnteteRapport f', sprintf('(%s)', $subSelect->getDQL()), 'latest', 'f.id = latest.ItemId');
I am not able to make this query work, I really need you guys.
Thank you in advance, you're awesome!

Seems like subqueries in joins do not work in dql. (I get error message: [Semantical Error] line 0, col 52 near '(SELECT e': Error: Class '(' is not defined.)
You could run $em->getConnection()->prepare($yourRawSql)->execute()->fetchAll(), but this returns a raw result array (no objects). See raw sql queries on knp-lab
Or do it in dql with HAVING instead of a subquery join:
$qb->select('i', 'i')
->addSelect('MAX(e.dateCreation)', 'date')
->from('CATUParkBundle:Item', 'i')
->leftJoin('i.kit', 'k')
->leftJoin('i.rapportOrWhatEver', 'f')
->groupBy('e.id');

Related

Symfony 2 query builder join without relation (cross join)

I am trying to use the query builder to join 2 tables which have no relation.
Desired end result:
SELECT x, y
FROM x
JOIN y
Query builder code:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('x');
$qb->from('Test1', 'x');
$qb->join('Test2', 'y');
$qb->orderBy('x.name', 'ASC');
Produces the following DQL:
SELECT x FROM Test1 x INNER JOIN Test2 y ORDER BY x.name ASC
Which results in a syntax error:
[Syntax Error] line 0, col 137: Error: Expected Literal, got 'BY'
The entities Test1 and Test2 don't have a relation (not in the code, nor in the database).
Is there any way to accomplish this?
I would like to use the query builder, because I have a lot of other functionality for the query that depends on the query builder (for filtering and sorting etc.).
I know this is possible with plain SQL, or DQL queries (not produced by the query builder).
You can try the following possibility:
public function getYourData($users) {
$qb = $this->entityManager->createQueryBuilder();
$qb
->select('x', 'y')
->from('Test1', 'x')
->leftJoin(
'Test2',
'y',
\Doctrine\ORM\Query\Expr\Join::WITH,
'x.id = y.reference_id'
)
->orderBy('x.name', 'ASC');
return $qb->getQuery()->getResult();
}

Symfony createQueryBuilder with many to many

In my system candidate and profession has many to many relationship. I need to implement following query in symfony.
SELECT c. *
FROM candidate AS c
LEFT JOIN candidate_profession AS cp ON cp.candidate_id=c.id
WHERE cp.profession_id = 2
So i wrote following code.
$matched = $em->getRepository('AppBundle:Candidate')
->createQueryBuilder('c')
->where('c.professions = :profession')
->setParameter('profession', $job->getProfession())
->getQuery()
->getResult();
$job->getProfession() is return profession object. But it show following error.
[Semantical Error] line 0, col 51 near 'professions =': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
How i implement that query?
I think your query should look like this:
$em->getRepository('AppBundle:Candidate')
->createQueryBuilder('c')
->leftJoin('c.professions', 'p')
->where('p.id = :profession')
->setParameter('profession', $job->getProfession())
->getQuery()
->getResult();
More about join clauses: http://doctrine-dbal.readthedocs.org/en/latest/reference/query-builder.html#join-clauses
First of all, I don't know where you have placed that snippet of code but I strongly advice you to migrate it into a Repository if you don't have already done (but looking at code I'm not sure you have)
Second, you need to pass an ID as ->getProfession() (as you already noticed) will return the whole object and you don't need it
So your query should be like this
$matched = $em->getRepository('AppBundle:Candidate')
->createQueryBuilder('c')
->where('c.professions = :profession')
->setParameter('profession', $job->getProfession()->getId())
->getQuery()
->getResult();
Please pay attention
You didn't specify the cardinality of relationship between job and profession: if is a something-to-Many you can't simply use ->getId() as returned object is an ArrayCollection, so, in that case, you need to do a loop to extract all id(s) and then use something like "IN" clause

How to make JOIN with OR expression in DQL?

Here is the SQL equivalent of what I expect to get from Doctrine:
SELECT c.* FROM comments c
LEFT JOIN articles a
ON a.id = c.articles_id OR a.translation = c.articles_id
WHERE c.published = 1 AND c.language = a.language
The problem is that I cannot make Doctrine to generate the JOIN operation with OR as it is supposed to be. If we execute query from the following QueryBuilder object:
$qb->select('c')
->from('Project:Comment', 'c')
->leftJoin('c.article', 'a', 'WITH', 'a = c.article OR a.translation = c.article')
->where('c.published = true AND c.language = a.language');
we receive the following SQL statement:
SELECT
...
FROM comments c0_
LEFT JOIN articles a0_ ON c0_.articles_id = a0_.id
AND (
a0_.id = c0_.articles_id OR
a0_.translation = c0_.profiles_id
)
WHERE c0_.published = 1 AND c0_.language = a0_.language
which is obviously not the same as the initial query, as WITH operator seems to add additional conditions to the basic one instead of replacing the whole condition.
Is there any way to force Doctrine to output exactly what I need? I know that I may use native SQL but I doubt that it will be as convenient as QueryBuilder. Maybe there is a way to extend Doctrine with normal JOIN ON implementation instead of this odd JOIN WITH?
Doctrine doesn't implement this because it is (as far as I understand at least) not considered optimized enough for SQL.
See this SO post for precisions.
What you intend to do could appearantly be done using Union and other types of Join.

Semantical Error - Doctrine 2.0

These are my tables:
Table Gift:
-id
-price
...
Table Couple:
-id
-name
...
table registry: //provide a many-many relation between gifts and couples
-id
-coupleId
-giftId
table purchase:
-amount
-registryId
I already wrote a sql query to get all the gift info for a specific couple
$qb = $this->createQueryBuilder('g') //gift
->from('\BBB\GiftBundle\Entity\Registry', 'reg')
->select('g.id , g.price')
->where('reg.gift = g.id')
->andWhere('reg.couple = :coupleID')
->orderBy('reg.id','DESC')
->setParameter('coupleID', $coupleID);
OR
SELECT g.id , g.price,
FROM gift g, registry reg
WHERE reg.gift_id = g.id AND reg.couple_id = 1
I also want to get the total amount that for the gifts that have been bought (if any)
EX. SUM(purchase.amount) as totalContribute
I have tried:
$qb = $this->createQueryBuilder('g')
->from('\BBB\GiftBundle\Entity\Purchase', 'p')
->from('\BBB\GiftBundle\Entity\Registry', 'reg')
->select('g.id , g.price')
->addSelect('SUM(p.amount) as totalContribute')
->leftJoin('p','pp', 'ON','reg.id = pp.registry')
->where('reg.gift = g.id')
->andWhere('reg.couple = :coupleID')
->orderBy('reg.id','DESC')
->setParameter('coupleID', $coupleID);
but it gives me the following error:
[Semantical Error] line 0, col 145 near 'pp ON reg.id': Error: Identification Variable p used in join path expression but was not defined before.
First of all, you should define join condition in your SQL statements after joins, not in WHERE clause. The reason is that it's really not efficient. So the query shoul look like:
SELECT g.id , g.price,
FROM gift g JOIN registry reg ON reg.gift_id = g.id
WHERE reg.couple_id = 1
But about your Doctrine query, You get error because you're defining joins in wrong way. Your query should more like:
$qb = $this->createQueryBuilder('g') // You don't have put "from" beacuse I assume you put this into GiftRepository and then Doctrine knows that should be \BBB\GiftBundle\Entity\Gift
->select('g.id , g.price')
->addSelect('SUM(p.amount) as totalContribute')
->join('g.purchase','p') // pay attention for this line: you specify relation basing on entity property - I assume that is "$purchase" for this example
->leftJoin('p.registry', 'reg') // here you join with \BBB\GiftBundle\Entity\Purchase
->where('reg.couple = :coupleID')
->orderBy('reg.id','DESC')
->setParameter('coupleID', $coupleID);
Please treat this as pseudocode - I didn't check it works but it should like more like this.
One more thing - if your repository method returns object(s) of X entity you should put this method to XRepository.

Symfony2 Doctrine SQL object mapping on subselect

I'm trying to use this query:
MySQL SELECT DISTINCT by highest value
SELECT
p.*
FROM
product p
INNER JOIN
( SELECT
magazine, MAX(onSale) AS latest
FROM
product
GROUP BY
magazine
) AS groupedp
ON groupedp.magazine = p.magazine
AND groupedp.latest = p.onSale ;
Within Symfony2 and DQL.
I have:
$query = $em->createQuery("SELECT p FROM MyBundle:Product p WHERE p.type = 'magazine' AND p.maglink IS NOT NULL OR (p.type = 'magazine' AND p.diglink IS NOT NULL) GROUP BY p.magazine ORDER BY p.onSale DESC");
Which works fine with and outputs objects but without the correct MAX(onSale)
Doing:
$query = $em->createQuery("SELECT p , MAX(p.onSale) FROM MyBundle:Product p WHERE p.type = 'magazine' AND p.maglink IS NOT NULL OR (p.type = 'magazine' AND p.diglink IS NOT NULL) GROUP BY p.magazine ORDER BY p.onSale DESC");
Results in non-objects being returned.
This:
$query = $em->createQuery("SELECT
p.*
FROM
MyBundle:Product p
INNER JOIN
( SELECT
p.magazine, MAX(onSale) AS p.latest
FROM
MyBundle:Product p
GROUP BY
p.magazine
) AS groupedp
ON groupedp.magazine = p.magazine
AND groupedp.latest = p.onSale ;");
Throws this error:
[Semantical Error] line 0, col 127 near 'SELECT
': Error: Identification Variable ( used in join path expression but was not defined before.
I assume due to this Symfony2 Doctrine query
How can I maintain my mapping while still being able to sort each item by onsale?
Have you considered splitting this into two queries:
First:
Run your query directly against the database connection, and return the IDs of the relevant rows, in the order you want. This separates your complex query from DQL.
Second:
Query the rows through Doctrine to get the full entities you want, based on the IDs/orders of the previous query.
This is a shot in the dark, but it seems fairly obvious. Your aliasing two tables to the same alias. Thus when your using p. in the join it thinks your working from the original definition of p from before the join, then you alias the join as p. Change the alias of the join (and the references to that table) so each alias is unique.

Resources