Doctrine2 "order by" before "group by" - symfony

I know that similar question was asked 3 years ago (Doctrine2 Order By before Group By) but maybe from that time something changed.
i have 2 entity propertes:
orderId
orderUpdateDate
both as a pair are set to be unique, but for listing I need last update date so sorting should be
order by orderUpdateDate DESC
and then grouped by orderId so I have orders only once.
group by orderId
problem is such that when I paste both in one query in query builder I got random listing of orders

To achieve such a result you'd have to end up with following SQL query:
SELECT n.id, n.update_date
FROM (
SELECT o.id, o.update_date
FROM orders o
ORDER BY o.update_date DESC
) n
GROUP BY n.id;
Unfortunately Doctrine's DQL doesn't support nested queries in FROM clause. However, it allows you to create a custom, native SQL query that can be mapped by ORM:
$rsm = new ResultSetMappingBuilder($em);
$rsm->addRootEntityFromClassMetadata('MyProject\\Order', 'o');
$sql = sprintf('SELECT %s FROM (SELECT ...) GROUP BY ...', $rsm->generateSelectClause());
$query = $em->createNativeQuery($sql, $rsm);
$result = $query->getResult();

Related

DQL OneToMany query and performing average

In a OneToMany relationship between the entities:
Composer > Soundtrack
Soundtrack > SoundtrackRating
i want to select each Soundtrack by a given Composer and avergage the SoundtrackRatings for each Soundtrack.
In DQL:
SELECT s.name, (SUM(r.rating)/COUNT(r.rating)) AS rating
FROM Soundtrack AS s
LEFT JOIN SoundtrackRating AS r
WHERE s.composer = :composer AND r.soundtrackId = s.id
GROUP BY s.id
ORDER BY rating DESC
Then i'm passing the :composer instance of type Composer:
$em->createQuery($dql)
->setParameter('composer', $composer)
->getResult();
However in the query result i am getting all Soundtrack entries, no matter what $composer i pass as a parameter. The only difference is that only averages the ratings of the given $composer, the rest averages 0.
Where is the problem in this query?
Figured it out. Removing r.soundtrackId = s.id from the WHERE clause and using WITH in the LEFT JOIN instead:
SELECT s.name, (SUM(r.rating)/COUNT(r.rating)) AS rating
FROM Soundtrack AS s
LEFT JOIN SoundtrackRating AS r WITH r.soundtrackId = s.id
WHERE s.composer = :composer
GROUP BY s.id
ORDER BY rating DESC

Doctrine ManyToMany Query

I have a Product Entity which has a ManyToMany relationship with a Taxon Entity. I want to find all the products that belong to the intersection of all Taxons. For instance I want to find all products that belong to taxons with IDs 1 and 2.
Products
{1,2,3}
Taxons
{1,2,3,4,5}
ProductsToTaxons
{{p1,t1},{p1,t2}, {p2,t2}}
I want to retrieve the following set ONLY when querying products for taxons 1 and 2:
Product
{1}
which is from {{p1,t1}, {p1,t2}}
Okay, So here is the DQL that i tried... but it doesn't work?
SELECT p FROM SRCProductBundle:Product p
JOIN p.taxons t
WHERE t.id = 1 AND t.id = 2
(P.S. I would also do this with QueryBuilder with as well)
EDIT
To clarify, here is the SQL that I would like to translate into DQL/QueryBuilder.
select p.id
from product p
where exists (select product_id
from product_to_taxon
where taxon_id = 1
and product_id = p.id)
and exists (select product_id
from product_to_taxon
where taxon_id = 4
and product_id = p.id);
You can use the MEMBER OF statement to achieve this although in my experience it hasn't produced very performant results with a ManyToMany relationship:
In DQL:
SELECT p FROM SRCProductBundle:Product p
WHERE 1 MEMBER OF p.taxons OR 2 MEMBER OF p.taxons
Or with Query builder
$this->createQueryBuilder('p')
->where(':taxon_ids MEMBER OF p.taxons')
->setParameter('taxon_ids', $taxonIdsArray)
->getQuery()
->getResult();
This will create SQL similar to the example provided although in my experience it still had a join in the EXISTS subqueries. Perhaps future versions of Doctrine can address this.
I think you want something like this:
$qb = $this
->createQueryBuilder('p')
->select('p.id')
;
$qb
->leftJoin('p.taxons', 'taxon1', Join::WITH, 'taxon1.id = :taxonId1')
->setParameter('taxonId1', 1)
->andWhere($qb->expr()->isNotNull('taxon1'))
->leftJoin('p.taxons', 'taxon2', Join::WITH, 'taxon2.id = :taxonId2')
->setParameter('taxonId2', 2)
->andWhere($qb->expr()->isNotNull('taxon2'))
;
Which is equivalent to the SQL:
SELECT p.id
FROM products p
LEFT JOIN taxons t1 ON (p.id = t1.product_id AND t1.id = 1)
LEFT JOIN taxons t2 ON (p.id = t2.product_id AND t2.id = 2)
WHERE t1.id IS NOT NULL
AND t2.id IS NOT NULL
;
Your DQL has wrong logic. You can't have a taxon with both id=1 and id=4. You could do it like this:
SELECT p FROM SRCProductBundle:Product p
JOIN p.taxons t
WHERE t.id = 1 OR t.id = 4
But I would prefer this way:
SELECT p FROM SRCProductBundle:Product p
JOIN p.taxons t
WHERE t.id IN (1, 4)
Using query builder that would look something like this, assuming you're in EntityRepository class:
$this->createQueryBuilder('p')
->join('p.taxons', 't')
->where('t.id IN :taxon_ids')
->setParameter('taxon_ids', $taxonIdsArray)
->getQuery()
->getResult();
For lack of a clean way to do this with DQL, and after a considerable amount of research, I resorted to doing this in Native SQL. Doctrine allows Native SQL via the EntityManager with createNativeQuery().
So in short, I utilized this ability and constructed the SQL query included in my question as a string and then passed it to the createNativeQuery() function.
This does appear to have some drawbacks as it appears I will be unable to use the KnpPaginatorBundle with it... So I might end up just filtering the results in PHP rather than SQL, which I'm hesitant to do as I think there are performance drawbacks.

selecting only max clause without group by properties in subquery using Nhibernate

I have SQL query like this:
select * from dbo.table1 where Id in
(
select max(id) as id from dbo.table1 group by prop1, prop2, prop3
)
I want to create NHibernate query which is be able to do this for me. I tried to use QueryOver but it doesn't work. Do you have any suggestions how to do it?
NHibernate supports even this kind of queries. Please, see more in documentation: 15.8. Detached queries and subqueries. We just have to split the query (as in your SQL snippet) into two parts:
inner select
the select with the IN clause
Let's assume, that the dbo.table1 in the Questin is mapped into MyEntity.
To create inner select, let's use the DetachedCriteria
EDIT (extended with the Group by, SqlGroupProjection)
There is an extract of the SqlGroupProjection method:
A grouping SQL projection, specifying both select clause and group by
clause fragments
// inner select
DetachedCriteria innerSelect = DetachedCriteria
.For(typeof(MyEntity))
.SetProjection(
Projections.ProjectionList()
.Add(
Projections.SqlGroupProjection(
" MAX(ID) ", // SELECT ... max(ID) only
" Prop1, Prop2, Prop3", // GROUP BY ... property1, p2...
new string[] {"ID"}, // could be empty, while not used for
new IType[] { NHibernate.NHibernateUtil.Int32 } // transformation
)
)
;
Note: I've provided even the last two paramters, but in this case they could be empty: new string[], new IType[] {}. These are used only for Transformation (materialization from data into entity). And this is not the case, we are just building inner select...
// the select with IN clause
var result = session.CreateCriteria(typeof(MyEntity))
.Add(Subqueries.PropertyIn("ID", innerSelect))
.List<MyEntity>();
Also related could be 15.7. Projections, aggregation and grouping

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.

How to filter old entries with unique id out of SQL query

I have a table and a relation
I have maybe 10 Submissions, but when I query the database I only want to get those with a Unique CaseId and the one to return should be the one with the newest Date. Is it possible (And adviceable) to do this in a single query or should I do the filtering in my asp.nets code behind where I fetch the data?
Edit: New images
Here you can see that I show many items with the same case id, I only want to show the latest one (Based on date)
This is my current sql query
SELECT Submission.Id, Date, center.Name as CenterName, center.Id as CenterId, subject.Name as SubjectName, subject.Id as SubjectId, EmployeeName, Reason, Description, Explanation, Done, ChiefLevel, Action, CaseId
FROM Submission, subject, center
WHERE center.Id=CenterId AND subject.Id=SubjectId
ORDER BY Date DESC;
SELECT caseid
FROM
(
SELECT caseid, max(date) AS max_date
FROM submission
GROUP BY caseid
) a
JOIN subject t ON a.subjectid=t.id
My QUERY ended up being this
SELECT s.Id, s.Date, c.Name as CenterName, c.Id as CenterId, su.Name as SubjectName, su.Id as SubjectId, s.EmployeeName, s.Reason, s.Description, s.Explanation, s.Done, s.ChiefLevel, s.Action, s.CaseId
FROM submission as s
INNER JOIN
(
SELECT CaseId, MAX(Date) AS MaxDateTime
FROM submission
GROUP BY CaseId
) as groupeds
ON s.CaseId = groupeds.CaseId
AND s.`Date` = groupeds.MaxDateTime
INNER JOIN
(
SELECT Id, Name
FROM subject
) as su
ON su.Id=SubjectId
INNER JOIN
(
SELECT Id, Name
FROM center
) as c
ON c.Id=CenterId;

Resources