I want to implement the following SQL query with QueryDSL:
SELECT
*
FROM
a
ORDER BY (
SELECT
COUNT(*)
FROM
b
WHERE
b.a_id = a.id
AND b.c = 1) DESC
If I omit the criteria "b.c = 1", it's quite easy:
selectFrom(a).orderBy(a.bs.size().desc());
But I can't find a way (simple or not) to include a criteria in the subquery.
I want my QueryDSL query to return a List<A> and not Tuple, if possible.
Thanks in advance !
Model
I have the following model with three classes. Class A contains 1 B, and can contain 0 or more C's. Both classes B & C contain amounts that i want to sum up together inside the scope of A.
class TableA {
#Id
Id id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "table_b_id", nullable = false)
TableB tableB;
#OneToMany(mappedBy = "tableA", fetch = FetchType.LAZY)
Set<TableC> tableCs;
}
class TableB {
#Id
Id id;
#Column(name = "amount_b")
Long amountB;
}
class TableC {
#Id
Id id;
#Column(name = "amount_c", nullable = false)
Long amountC;
#JoinColumn(name = "table_a_id")
TableA tableA;
}
Question
I am tasked to calculate the composition of two values "sum(c.amount) + b.amount" and see whether they are within a defined min/max interval, e.g. [0, 100]. The result is a List of A's that comply to this condition. I'm being asked to do it in Hibernate Criteria only.
For that i have constructed a SQL statement that expresses this requirement:
select compositeSum from
(SELECT sum(tableC.amountC) + tableB.amountB as 'compositeSum'
FROM tableA A
left join tableB B on A.b_id = B.id
left join tableC C on C.a_id = A.id) SUBQUERY
where compositeSum between 0 and 100;
Attempt
Since there is an aggregate function "sum()" and also an arithmetic operation "+", i have tried using subqueries to solve the problem.
The closest i got was the following solution:
Long min = 0l;
Long max = 100l;
DetachedCriteria subquery = DetachedCriteria.forClass(TableA.class, "inner")
.createAlias("tableC", "tableC", JoinType.LEFT_OUTER_JOIN)
.createAlias("tableB", "tableB")
.setProjection(Projections.sqlProjection("coalesce(sum(amountC), 0) + amountB", new String[] {"compositeSum"}, new Type[] {StandardBasicTypes.LONG}));
Criteria criteria = session().createCriteria(TableA.class, "outer")
.add(Subqueries.ge(max, subquery))
.add(Subqueries.le(min, subquery));
Issue
The problem is that this translates into a subquery used in the where-clause.
select * from A this_
where ? >=
(select sum(amountC) + amountB from table_A tableA_
inner join table_B tableB2_ on tableA_.table_b_id=tableB2_.id
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id)
and ? <=
(select sum(amountC) + amountB from table_A tableA_
inner join tableB tableB2_ on tableA_.table_B_id=tableB2_.id
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id)
I wanted to build a subquery that can be fed to the from-clause of the main query. Instead i got a subquery that is fed to the where-clause. This is not what i wanted, and results in strange results.
Does anyone know whether it is possible to feed a subquery to a from-clause in Hibernate Criteria? Many thanks!
Solution
(updated)
Apparently, adding an additional filter inside the inner query that points to the outer query solved the issue. To refer to the parent query entity from the subquery, use the magic keyword "this".
Long min = 0l;
Long max = 100l;
DetachedCriteria subquery = DetachedCriteria.forClass(TableA.class, "inner")
.createAlias("tableC", "tableC", JoinType.LEFT_OUTER_JOIN)
.createAlias("tableB", "tableB")
.setProjection(Projections.sqlProjection("coalesce(sum(amountC), 0) + amountB", new String[] {"compositeSum"}, new Type[] {StandardBasicTypes.LONG}))
.add(Restrictions.eqProperty("id", "this.id"));
Criteria criteria = session().createCriteria(TableA.class, "outer")
.setProjection(Projections.property("id"))
.add(Subqueries.ge(max, subquery))
.add(Subqueries.le(min, subquery));
The logic behind adding an additional filter is that the min/max is a scalar, and you want the subquery to return another scalar, and not a list, to compare against, which would otherwise result in erratic behavior.
This then becomes a correlated subquery:
select * from A this_
where ? >=
(select sum(amountC) + amountB from table_A tableA_
inner join table_B tableB2_ on tableA_.table_b_id=tableB2_.id
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id
where tableA_.id = this_.id)
and ? <=
(select sum(amountC) + amountB from table_A tableA_
inner join tableB tableB2_ on tableA_.table_B_id=tableB2_.id
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id
where tableA_.id = this_.id)
This solution is a different kind of subquery, and is probably a bit slower since you are executing the subquery twice, but it does the job.
Having-clause
Note that a SQL solution would also be possible using a having-clause.
Below, you can find the conversion of the above into a having-clause.
The only problem is that Hibernate Criteria does not support having-clauses.
SELECT coalesce(sum(amountC),0) + amountB as 'compositeSum'
FROM table_A tableA
left join table_B tableB on tableA.table_b_id = tableB.id
left join table_C tableC on tableC.table_a_id = tableA.id
group by m.id
having compositeSum between 0 and 100
You don't need sub query. Simplify your query like below:
SELECT sum(tableC.amountC) + tableB.amountB as 'compositeSum'
FROM tableA A
left join tableB B on A.b_id = B.id
left join tableC C on C.a_id = A.id
where (sum(tableC.amountC) + tableB.amountB) between 0 and 100;
and now change you hibernate code accordingly.
Apparently, adding an additional filter inside the inner query that points to the outer query solved the issue. To refer to the parent query entity from the subquery, use the magic keyword "this".
Long min = 0l;
Long max = 100l;
DetachedCriteria subquery = DetachedCriteria.forClass(TableA.class, "inner")
.createAlias("tableC", "tableC", JoinType.LEFT_OUTER_JOIN)
.createAlias("tableB", "tableB")
.setProjection(Projections.sqlProjection("coalesce(sum(amountC), 0) + amountB", new String[] {"compositeSum"}, new Type[] {StandardBasicTypes.LONG}))
.add(Restrictions.eqProperty("id", "this.id"));
Criteria criteria = session().createCriteria(TableA.class, "outer")
.setProjection(Projections.property("id"))
.add(Subqueries.ge(max, subquery))
.add(Subqueries.le(min, subquery));
The logic behind adding an additional filter is that the min/max is a scalar, and you want the subquery to return another scalar, and not a list, to compare against, which would otherwise result in erratic behavior.
This then becomes a correlated subquery:
select * from A this_
where ? >=
(select sum(amountC) + amountB from table_A tableA_
inner join table_B tableB2_ on tableA_.table_b_id=tableB2_.id
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id
where tableA_.id = this_.id)
and ? <=
(select sum(amountC) + amountB from table_A tableA_
inner join tableB tableB2_ on tableA_.table_B_id=tableB2_.id
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id
where tableA_.id = this_.id)
Having-clause
Note that a SQL solution would also be possible using a having-clause.
Below, you can find the conversion of the above into a having-clause.
The only problem is that Hibernate Criteria does not support having-clauses.
SELECT coalesce(sum(amountC),0) + amountB as 'compositeSum'
FROM table_A tableA
left join table_B tableB on tableA.table_b_id = tableB.id
left join table_C tableC on tableC.table_a_id = tableA.id
group by m.id
having compositeSum between 0 and 100
I've two tables:
The table A named "Prova" has the following columns: id, id_comitato, id_comitato_erog
The table B named "comitato" has the following columns: id_comitato, name
Criteria:
Criteria criteria = mySession.createCriteria(Prova.class, "p");
criteria.createCriteria("comitato", "c", CriteriaSpecification.LEFT_JOIN);
Translate this query in SQL is:
SELECT * FROM Prova p LEFT JOIN comitato c ON p.id_comitato=c.id_comitato
But what I want is the following:
SELECT * FROM Prova p LEFT JOIN comitato c ON p.id_comitato_erog=c.id_comitato
How can I specify this join condition?
Thank you very much,
have a good day!
Have you tried something like this -> Yet another post on Stackoverflow
.Basically you would have to do something similar to the following:
Criteria criteria =
mySession.createCriteria(Prova.class, "p")
.createAlias("comitato",
"c",
Criteria.LEFT_JOIN,
Restrictions.eqProperty("p.id_comitato_erog",
"e.id_comitato"));
I want to do something like that with HQL:
SELECT *
FROM tableA a
INNER JOIN (select fieldA, sum(fieldB) as sum from tableB) b
ON a.fieldA = b.fieldA and a.fieldC = b.sum;
But this gives an error:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: (...
There is any way to make this using HQL and Hibernate?
try the native SQL solution approach:
need toimport this first:
import org.hibernate.SQLQuery;
then somewhere in your code:
SQLQuery query = session.createSQLQuery(
"SELECT * FROM tableA a
INNER JOIN
(SELECT fieldA, sum(fieldB) as sum from tableB) b
ON a.fieldA = b.fieldA and a.fieldC = b.sum"
);
more on this link
and HERE (
Joins in Hibernate Query Language)
You can try to do such thing with HQL:
String sqlText =
"select entityA
from EntityA entityA, EntityB entityB
where entityA.fieldA=entityB.fieldA
and entityA.fieldC=(select sum(entityB.fieldB)
from EntityB entityB
where entityB.fieldA=entityA.fieldA)"
Query query = session.createQuery(sqlText);
It should work similar to your sql. About your statement - as I know you cannot use inner view in HQL because it is object oriented.
Here is a good article about joins in HQL.
EDIT:
According to notes from user1495181 above query can be rewritten like (but I'm not sure):
String sqlText =
"select entityA
from EntityA entityA
join entityA.entitiesB entityB
Where entityA.fieldC=(select sum(entityB.fieldB)
from EntityB entityB
where entityB.fieldA=entityA.fieldA)"
But I prefer first variant because as for me it is more understandable (especially for peoples who used to work with native SQL).
You cannot define the join with on keyword. Hibernate know how to do the join based on your mapping.
If you define a relation in the mapping between a and b than hibernate will do the join based on the relation that you defined.
If you have relation between a and b than do inner join without using on and put the join criteria in the where clause
I have the following HQL query which works fine, however it returns a list of full FooD objects. I only need the ID of the FooD objects as I need to have faster query. Please not that in Hibernate mappings, FooD has a many-to-one relationship with FooB.
hqlQuery = "from FooD d left join fetch d.bill where d.ts < :ts"
I have then tried to get only the ID using the same kind of HQL query:
hqlQuery = "SELECT d.id from FooD d left join fetch d.bill where d.ts < :ts"
I got a "query specified join fetching, but the owner of the fetched association was not present in the select list".
I have then converted the query to regular Oracle SQL to get only FooD.ID:
sqlQuery = "SELECT d.id from FooD d LEFT OUTER JOIN FooB b on d.foodId=b.id where d.ts < :ts"
I have then mapped FooD and FooB objects like this:
sqlQuery.addEntity(FooD.class);
sqlQuery.addEntity(FooB.class);
and then get the resulting list by calling:
hSession.createSQLQuery(sql).setTimestamp("ts", ts).list();
But got the following error: "unexpected token: on near line 1".
Does someone know how to do get only the ID of FooD when doing a left outer join on FooB using Hibernate?
Update:
I didn't test it, but this should do the trick
SELECT d.id from FooD d inner join d.bill where d.ts < :ts
when you add LEFT you make it an outer join implicitly, and there is no need to initialize bill if all you need is to join by keys
Hibernate requires the object to be in the select clause to do any eager join fetches on it
But since you have no select or where clauses on d.bill, why do you need to fetch it anyway?
If all you need is the id, why not do this, there is no reason for the redundant join:
hqlQuery = "SELECT d.id from FooD d where d.ts < :ts"