Doctrine Query Builder, "between" expression and subquery - symfony

I'm trying to create a quite complex query with Doctrine's Query Builder (I'm using Doctrine 2.2). In the model, i have a Distributor class and a DistributorVisit class with a one-to-many relationship. Every time a Distributor is visited by a representative, a new row is added to the DistributorVisit table with the visit date. An ER diagram of the two tables can be found here.
Now i want to be able to filter Distributors by their last visit date. So the user enters a date range (last visit from and last visit to) and Distributors whose last visit date is between those two dates are listed. I'm using Doctrine's Query Builder because there are a lot of other conditional queries I do in the filter, and I found the object-oriented approach to work best in this scenario. Here's what i did in the DistributorRepository class:
$qb = $this->getEntityManager()->createQueryBuilder()
->select('o')
->from('MyBundle:Distributor', 'o');
// Lots of 'andWhere's here
$qbv = $this->getEntityManager()->createQueryBuilder();
$qb->andWhere($qb->expr()->between(
$qbv->select($qbv->expr()->max('v.visitDate'))
->from('MyBundle:DistributorVisit', 'v')
->join('MyBundle:Distributor', 'o2',
Join::WITH,
$qbv->expr()->andX(
$qbv->expr()->eq('o2.id', 'v.distributorId'),
$qbv->expr()->eq('o2.id', 'o.id')
))
->getDQL(),
$filter->getLastVisitFrom()->getTimestamp(),
$filter->getLastVisitTo()->getTimestamp()
));
This gives me the following error:
[Syntax Error] line 0, col 83: Error: Expected Literal, got 'SELECT'
I guess this is because the Query Builder expects a literal where my sub-select is, however, the result of the sub-query should be a literal, right? May it be because the Query Builder does not add parenthesis accordingly?
Thanks a lot for your help.

I now resolved the issue the following way:
$qb = $this->getEntityManager()->createQueryBuilder()
->select('o')
->from('MyBundle:Distributor', 'o');
$qbdv = $this->getEntityManager()->createQueryBuilder();
$qbdv->select('MAX(dv2.visitDate)')
->from('MyBundle:DistributorVisit', 'dv2')
->where($qbdv->expr()->eq('dv2.distributor', 'o'));
$maxVisitDate = '('.$qbdv->getDQL().')';
$qb->leftJoin(
'o.distributorVisits',
'dv',
Join::WITH,
$qb->expr()->eq('dv.visitDate', $maxVisitDate)
);
$qb->andWhere(
$qb->expr()->between(
'dv.visitDate',
':dateFrom',
':dateTo'
)
)
->setParameter('dateFrom', $filter->getLastVisitFrom())
->setParameter('dateTo', $filter->getLastVisitTo());
So what I basically did is the following: I joined the DistributorVisit table to the Distributor table with the maximum visit date. The trick was the fact that one can pass the DQL of a (sub-)query ($qb1->getDQL()) directly to a Doctrine expression ($qb2->expr()->eq('column', $qb1->getDQL()). I did this with the left join in the code above.

I guess that your current DQL looks like this:
SELECT ..
FROM ..
WHERE SELECT .. FROM .. BETWEEN .. AND ..
But should look like this:
SELECT ..
FROM ..
WHERE (SELECT .. FROM ..) BETWEEN .. AND ..
To fix your code, i'd just put the subquery's dql inside parentheses:
$subQueryDQL = $qbv->select($qbv->expr()->max('v.visitDate'))
->from('MyBundle:DistributorVisit', 'v')
...
->getDQL();
$qb->andWhere($qb->expr()->between(
sprintf('(%s)', $subQueryDQL),
$filter->getLastVisitFrom()->getTimestamp(),
$filter->getLastVisitTo()->getTimestamp()
));

My case:
$qb->andWhere(
"t.field BETWEEN (
{$subQuerybuilder1->getDQL()}
) AND (
{$subQuerybuilder2->getDQL()}
)
");
Got:
SELECT ... WHERE t.field BETWEEN (
SELECT t1.field FROM t1
) AND (
SELECT t2.field FROM t2
)

Related

DQL Request with LeftJoin

I want to count how many times my theme is used in my Blog.
Any blog posts (articles in french) can use one or more theme.
So I have a table (ManyToMany) :
themes_articles (id_theme,id_article) And :
theme (id_theme,nom_theme)
articles (blog posts) (id_article, description ...)
In SQL, I do :
SELECT T.id,nom_theme,count(A.themes_id) from themes_articles A right join themes T on T.id=A.themes_id group by nom_theme
It works, but when I want to use right join in DQL, it's a little bit hard. I switched my two tables for use a left join but I don't know how I can use my relation table here (themes_articles).
I tried something like this :
$query = $this->createQueryBuilder('')
->select(array('T.id', 'nomTheme', 'count(A.themes_id) as nombre'))
->from('themes', 'T')
->leftJoin('themes_articles', 'A', 'WITH', 'T.id= A.themes_id')
->groupBy('nom_theme');
return $query->getQuery()->getResult();
But it doesn't work.
[Semantical Error] line 0, col 93 near 'themes_articles': Error: Class 'themes_articles' is not defined.
How can I do to convert my SQL request in DQL request ?
Thank you a lot for any help.
Use like this
$query = $this->createQueryBuilder()
->select(array('t.id', 't.nomTheme', 'count(ta.themes_id) as nombre'))
->from('<YOUR BUNDLE>:<Theam Entity Class>', 't') //Like AcmeTheamBundle:Themes
->leftJoin('t.themes_articles')
->groupBy('t.nomTheme'); //better to use theam id
return $query->getQuery()->getResult();
It is a little bit better ! I had to add the name of the relation between Articles and themes (contient) in my request.
$query = $this->createQueryBuilder('t')
->select(array('t.id', 't.nomTheme', 'count(ta.id) as nombre'))
->leftJoin('t.contient', 'ta', 'WITH', 't.id= ta.id')
->groupBy('t.nomTheme'); //better to use theam id
return $query->getQuery()->getResult();

Symfony and Doctrine: order by time difference

I am trying to build a query that retrieves all the most recent and upcoming activities from database.
The entity activity has a field named date of type DateTime. So in my repository I was thinking of building something like this:
$query = $repository
->createQueryBuilder('a');
$query->orderBy( 'DATEDIFF( a.date, NOW())' , 'ASC');
$query->setMaxResults( 6 );
return $query;
Unfortunately I get the following error:
[Syntax Error] line 0, col 59: Error: Expected end of string, got '('
The Dql that is generated by my query:
SELECT a FROM MyBundle\Entity\Activity a ORDER BY DATEDIFF( a.date, NOW()) ASC
I also tried installing beberlei/DoctrineExtensions, but either it is not working or I was unable to configure it correctly.
Anyone has any suggestion?
Thanks in advance
date_diff si already implemented as Doctrine DQL statement as described here
for use as ordering statement I suggest you to use the HIDDEN select keyword as explained in this article
So your DQL is like this:
SELECT
a,
DATE_DIFF( a.date, CURRENT_TIMESTAMP() ) AS HIDDEN score
FROM MyBundle:Entity a
ORDER BY score
And add the max result on the query. Let me know if you need help to adapt as query builder statement
Hope this help
Why don't you just use
$query = $repository
->createQueryBuilder('a');
$query->orderBy( 'DATEDIFF( a.date, CURRENT_TIMESTAMP())' , 'ASC');
$query->setMaxResults( 6 );
return $query;
?

Where-Condition as IN(Subquery) with Doctrine2 in Symfony2.3.1 doesnt work

---- Done with Symfony2.3.1 and Doctrine2 ----
Sorry, i hope i was not too stupid to find a suitable solution for my problem. I try to build a Query for hours.
SELECT * FROM product
WHERE product_id in
(
SELECT product_id from (
SELECT count(*) as result_amount, product_id FROM product_x_attribut
JOIN productattribut on productattribut_id = productattribut.id
WHERE (
productkey = "price" and
stringType = "premium"
) or (
productkey = "size" and
stringType = "S"
)
GROUP BY product_id
HAVING result_amount = 2
) as temp
)
GROUP BY product_id
ORDER BY p0_.name ASC
This is the SQL which works fine in phpmyAdmin.
This can be seen like
Select * from abc where abc.x in ( Select * from ( select * from ) as abcd )
So there is one core query, i call it subSubQuery, the second query around the core will be called subQuery and the outer Query is just the outer Query, no a Subquery.
I could build the subSubQuery with Doctrine2.
But i cannot built the subQuery like this
Select product_id from ( $subSubQuery->getQuery()->getDQL() )
I want to do the subQuery like this
$subQuery = $repositoryProduct->createQueryBuilder('product');
$subQuery->add('select', 'product_id');
$subQuery->add('from',$subSubQuery->getDQL() );
// However to set an alias is a miracle for me, this didnt work off course
$subQuery->add('as','tmp' );
This is the subQuery.
I also cannot build the outer Query
Select * from abc where abc.x in ( $subQuery->getQuery()->getDQL() )
I want to do this like this
$query->where(
$query->expr()->in('product.id', $subQuery->getDQL() )
);
But i try to build this with Doctrine2 like this:
I am so down, i tried ->getSQL(), ->getDQL(), i tried as much as i was able to detect as a suitable tiny step to a solution for this problem and i has tried as much keyword in google as my finger were able to write... I hope someone could help me to find a solution...
Thanks a lot to each helpful advise.
I know that statements like this work:
$qbGames->andWhere($qbGames->expr()->in('game.id',$qbGameId->getDQL()));
Your question is kind of hard to follow. Consider using pastebin to show all your mappings as they currently exist. And then maybe presenting a simplieid query?
My $qbGameId query was built with:
$qbGameId = $em->createQueryBuilder();
$qbGameId->addSelect('distinct gameGameId.id');
$qbGameId->from('ZaysoCoreBundle:Event','gameGameId');
// Assorted joins and where conditions

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 join

Okay, so i've got a query that i've researched and researched how to get this to work and for the life of me i cant!... perhaps i'm just doing this incorrectly and the minimal information ive found..
I've got a table named timeclock setup.. which has a field: noteBy_id in it which is an id to the user the record belongs to...
What I need to do now, is for the management side of things in the system.. I anticipate more than 1 company using this timeclock system, and as such I need to filter the results based on the company id.. In the user table, i have a field named parentcompany_id
So, lets see if I can express in words what I need to do..
I need to Select * from timeclock and left join user.parentcompany_id where timeclock.daydate < :start and where u.parentcompany = :pid
where start is: `date('Y-m-d 00:00:00');
I've setup this query:
$em = $this->getDoctrine()->getEntityManager();
$start = date('Y-m-d 00:00:00');
$qb = $em->getRepository('EcsCrmBundle:TimeClock');
$qb = $qb->createQueryBuilder('t');
$query = $qb->select('t, u.parentcompany_id')
->from('timeclock', 't')
->leftJoin('Ecs\AgentManagerBundle\Entity\User', 'u', 'ON' 'u.id = t.noteBy_id AND u.parentcompany_id = :pid')
->where('t.daydate < :start')
->andWhere("t.noteBy_id != ''")
->setParameter('start', $start)
->setParameter('pid', $user->getParentcompany())
->getQuery();
$entities = $query->getArrayResult();
I've looked and looked and can't find a solution to the error that I get which is:
An exception has been thrown during the rendering of a template ("[Semantical Error] line 0, col 112 near 'u ON u.id = t.noteBy_id': Error: Identification Variable Ecs\AgentManagerBundle\Entity\User used in join path expression but was not defined before.") in EcsCrmBundle:TimeClock:manager.html.twig at line 5.
and the query that gets output is:
SELECT t, u.parentcompany_id FROM Ecs\CrmBundle\Entity\TimeClock t LEFT JOIN Ecs\AgentManagerBundle\Entity\User u ON u.id = t.noteBy_id AND u.parentcompany_id = :pid, timeclock t LEFT JOIN Ecs\AgentManagerBundle\Entity\User u ON u.id = t.noteBy_id AND u.parentcompany_id = :pid WHERE t.daydate < :start AND t.noteBy_id != ''
which under normal circumstances would work perfectly... but in this, it just doesn't... Any ideas?
I've recently had to do it like this.. I'm guessing in this that your noteBy is a ManyToOne in the user table and you are wanting to have it filter the results by the company of the admin that is currently logged into your system..
So, adapting a join I had to write myself for such a task is easy enough. I personally like to use the QueryBuilder so this will be done in query builder..
Your first mistake in your query is the ->from('timeclock', 't') line. Because you have previously created your object with $qb = $em->getRepository('EcsCrmBundle:TimeClock'); $qb = $qb->createQueryBuilder('t'); you don't need the from in the query builder, as it will be generated for you.
The next issue, is the leftJoin and I'll explain why when I've shown you a working version.
And the last issue, preventing this from working how you want it - is a missing andWhere clause. So, lets take a look at a working query.
$query = $qb->select('t, u')
->leftJoin('t.noteBy', 'u', 'WITH', 'u.id = t.noteBy')
->where('t.daydate < :start')
->andWhere('u.parentcompany = :pid')
->setParameter('start', $start)
->setParameter('pid', $user->getParentcompany())
->getQuery();
So because we've already created the object by using $qb = $qb->createQueryBuilder('t') we just select t and u
For the join, we're joining the timeclock table by the noteBy column, which is the user id from the user table. So, the first argument being the "from" alias. So, since we've aliased the timeclock table with t we use t.noteBy. The next argument in the leftjoin is the alias of the 2nd table, which is u in this case but can be anything.. The third argument for a leftJoin anyway - is the way you join it.. either a WITH or ON will work here. and the 4th argument, is the match you wish it to have.. in this case u.id must equal t.noteBy
You will see that I got rid of one of the andWhere, I did this because with the properly structured query you shouldn't need it. I did however add in the andWhere for the u.parentcompany since that is afterall what you are looking to filter by you should have it in a WHERE instead of as a match in the join itself.
The documentation is very limited in this, and it took me a while to figure it all out as well.. You, undoubtedly - like me, came to using doctrine from writing your queries by hand. And So since you seem to be just starting with Symfony (i am myself as well about 2 months in now), you're still in the hand-coding mindset. But with further time, you'll start understanding the DQL way of life. Try this query out and see what happens.
Ok, first you would need to relate entity Timeclock to Company. Whenever you want to join two entities in Doctrine they need to be related by some attribute (that is, table column).
I don't see any need for User entity in this query as all info is available through Company entity and you are not filtering down results based on any user properties.
You desired query should look something like this (more or less). I took liberty and ditched _id suffixes from entity attributes as they tend to cloud what is really going on. ;)
$query = $this->getEntityManager()->createQuery("SELECT t, c.id FROM EcsCrmBundle:TimeClock t JOIN t.company c WHERE c.id = :pid AND t.daydate < :start AND t.noteBy != ''");
$query->setParameter('start', $start);
$query->setParameter('pid', $user->getParentcompany());
return $query->getArrayResult();
Also, I did inner-join (JOIN) as I think there could not be timeclock without it's company but feel free to change that to LEFT JOIN if that suits you better.
Is this what you were trying to achieve?

Resources