Sub-Select In hibernate criteria - java

I have a sql table A with column names
name, id1, id2, val1
and a table B with column names
id1, id2, key1, key2
and this is my sql query
SELECT
v1.id1,
v1.id2
FROM (
SELECT
A.id1,
A.id2,
min(val1) AS x
FROM A
JOIN B ON A.id1 = B.id1 AND A.id2 = B.id2
GROUP BY A.id1, A.id2
) AS v1
WHERE v1.x > 10
using the DetachedCriteria i was able to form the sub-query
DetachedCriteria subCriteria = DetachedCriteria.forClass(A_model.class);
subCriteria.createAlias("b", "b_model");
subCriteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("id1"))
.add(Projections.groupProperty("id2"))
.add(Projections.min("val1"),"x");
but i am facing a hard time in creating the outer query.
any suggestion how can i create the criteria for the above SQL?
Thanks in anticipation.

Sub-selects in the from clause are not supported by Hibernate for the time being. However, your query can be rewritten in a simpler and more efficient form by utilizing the HAVING clause:
SELECT A.id1, A.id2,
FROM A JOIN B ON A.id1 = B.id1 AND A.id2 = B.id2
GROUP BY A.id1, A.id2
HAVING min(val1) > 10
The above query can be easily ported to HQL or Criteria.

Consider creating a view for the data you need:
create view A_B_BY_ID1_AND_ID2 as
select A.id1,
A.id2,
min( val1 ) as x
from A
join B on A.id1 = B.id1 and A.id2 = B.id2
group by A.id1,
A.id2
Then create a DTO to represent this data:
#Entity(table="A_B_BY_ID1_AND_ID2")
#Data //are you on board with lombok?
public class ABById1AndId2 {
#Column
private int x;
#Column
private int id1;
#Column
private int id2;
}
then access it like anything else:
session.createCriteria(ABById1AndId2.class).add(Restrictions.gt("x", 10)).list();

Select from Select is neither supported by HQL nor by Criteria object. The solution here would be Named Query.
#NamedNativeQueries({
#NamedNativeQuery(
name = "findV1",
query = "SELECT
v1.id1,
v1.id2
FROM (
SELECT
A.id1,
A.id2,
min(val1) AS x
FROM A
JOIN B ON A.id1 = B.id1 AND A.id2 = B.id2
GROUP BY A.id1, A.id2
) AS v1
WHERE v1.x > 10"
)
})

Related

QueryDSL order by subquery count

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 !

Select count of occurrences of each status in another table using jpa criteria

I need to select my main table and the number of occurrences of each status in another table using criteria api in just one query.
My current solution is in native query, which is working, but I want to do it in a more object-based way.
I tried doing it in criteria by using a specific query just to select all status and then count it manually. But with that approach, I'm calling two queries: One to fetch the details in my main table, and another to select all status where the id is same with main table.
Is there a more efficient way to do this?
Here is my native query (simplified):
SELECT * FROM(
SELECT a.id, a.type, b.count_pending, b.count_failed, b.count_processed
FROM CM AS a
LEFT JOIN ( SELECT
COUNT( CASE WHEN status = 'PENDING' THEN 1 ELSE NULL END ) count_pending,
COUNT( CASE WHEN status = 'FAILED' THEN 1 ELSE NULL END ) count_failed,
COUNT( CASE WHEN status = 'PROCESSED' THEN 1 ELSE NULL END ) count_processed
FROM CM_PARAM WHERE id_cm = :cmId
GROUP BY id_cm
) AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = :cmId) AS a
Here is my CM entity (simplified):
#Entity
public class Cm {
#Id
private Long idCm;
private String type;
// other fields
// setters and getters
}
Here is my CM_PARAM entity (simplified):
#Entity
public class CmParam {
#Id
private Long idCmp;
#ManyToOne
#JoinColumn(name = "id_cm")
private Cm cm;
private String status;
// other fields
// setters and getters
}
Using the native query approach, I can add transient fields in my Cm entity:
#Transient
private Long countPending;
#Transient
private Long countFailed;
#Transient
private Long countProcessed;
How can I do it using criteria api, and if possible in just one transaction.
The expected output would be somehow like this:
{
"idCm": 1,
"type": "sms",
"countPending": 5,
"countFailed": 3,
"countProcessed": 9
}
Your query can be rewritten without the subquery join:
SELECT
a.id_cm,
a.type
COUNT(CASE WHEN b.status = 'PENDING' THEN 1 ELSE NULL END) countPending,
COUNT(CASE WHEN b.status = 'FAILED' THEN 1 ELSE NULL END) countFailed,
COUNT( CASE WHEN b.status = 'PROCESSED' THEN 1 ELSE NULL END ) countProcessed
FROM CM AS a
LEFT JOIN CM_PARAM AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = ?1
GROUP BY a.id_cm, a.type
You will have to add the inverse side of the association to Cm:
#OneToMany(mappedBy = "cm")
private Set<CmParam> params;
(otherwise, you would need a RIGHT JOIN from CmParam to Cm, something Hibernate does not support)
The Criteria query then becomes:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<? extends Object[]> cq = cb.createQuery(new Object[0].getClass());
Root<Cm> a = cq.from(Cm.class);
Join<Cm, CmParam> b = a.join("params", JoinType.LEFT);
cq.where(cb.equal(a.get("idCm"), cb.parameter(Long.class, "idCm")));
cq.groupBy(a.get("idCm"), a.get("type"));
cq.multiselect(
a.get("idCm"),
a.get("type"),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "PENDING"), 1L)
.otherwise(cb.nullLiteral(Long.class))),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "FAILED"), 1L)
.otherwise(cb.nullLiteral(Long.class))),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "PROCESSED"), 1L)
.otherwise(cb.nullLiteral(Long.class))));
Note that the result is of type Object[]. If you want to use your current approach with transient fields, the easiest way would be to add the appropriate constructor to Cm and use the cb.construct() method:
cq.select(cb.construct(Cm.class, a.get("idCm"), a.get("type"), ...))
Note that:
if you'd rather not add the params field to Cm, but you're fine with an INNER JOIN, you can just use Root<CmParam> b = cq.from(CmParam.class) and Join<CmParam, Cm> a = b.join("cm") instead.
if in your actual query you're selecting more attributes from Cm than just cmId and status, you will probably need to list them all in groupBy as well

How can i convert a from-subquery into a Hibernate Criteria statement

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

How to select index colum in Hibernate?

I have a database table Communications with type, value and a foreign key as index that maps back to a Person table declared as follows:
#Table(name = 'communication', schema = 'schema')
#org.hibernate.annotations.Table(appliesTo = 'communication', indexes = {
#Index(name = "idx_communication_person_id", columnNames = { "person_id" })
}
)
And the Person object maps to this as:
#OneToMany(fetch = LAZY, cascade = ALL, orphanRemoval = true)
#JoinColumn(name = "person_id")
#OrderColumn
#Index(name = "idx_communication_person_id")
private final List<Communication> communications
Now I want to create a HQL query with Hibernate, that selects based on this index colum, like:
WHERE person.id in ( SELECT c.person_id FROM Communication c WHERE c.type = 3 AND c.value = 'john.doe#server.com' )
That doesn't work, because HQL doesn't know c.person_id at this point, because index columns are in general unknown to HQL.
How do I properly address the index in HQL, or if that is not possible: how do I write the statement to archive the same as the native-like query above?
EDIT: For performance reasons there must not be a JOIN in any form.
I think you need something like this:
SELECT p.* FROM person p
JOIN p.communication c
WHERE c.type = 3 AND c.value = 'john.doe#server.com'
That doesn't work, because HQL doesn't know c.person_id at this point, because index columns are in general unknown to HQL.
This doesn't make much sense to me.
If you want to have an HQL statement that returns a list of identifiers for Person based on some criteria, you can easily do it much like how your SQL statement is written.
SELECT p.id
FROM Communication c JOIN FETCH c.person p
WHERE c.type = :communicationType
AND c.value = :emailAddress
If you actually want persons, just write the query to select c.person rather than p.id in order to hydrate all Persons. In the following, the query allows you to specify a person identifier on the predicate if needed.
SELECT c.person
FROM Communication c JOIN FETCH c.person p
WHERE c.person.id = :personId
AND c.type = :communicationType
AND c.value = :emailAddress
UPDATE
If you don't want to use any joins, then simply expose the personId as a numeric value on your Communication entity without any association mappings.
public class Communication {
#Column(name = "personId", nullable = false, insertable = false, updatable = false)
private Long personId;
}
You should then be able to issue a query such as:
SELECT c.personId
FROM Communcation c
WHERE c.type = :communicationType
AND c.value = :emailAddress

How to create a JPA query with LEFT OUTER JOIN

I am starting to learn JPA, and have implemented an example with JPA query, based on the following native SQL that I tested in SQL Server:
SELECT f.StudentID, f.Name, f.Age, f.Class1, f.Class2
FROM Student f
LEFT OUTER JOIN ClassTbl s ON s.ClassID = f.Class1 OR s.ClassID = f.Class2
WHERE s.ClassName = 'abc'
From the above SQL I have constructed the following JPQL query:
SELECT f FROM Student f LEFT JOIN f.Class1 s;
As you can see, I still lack the condition OR s.ClassID = f.Class2 from my original query. My question is, how can I put it into my JPQL?
Write this;
SELECT f from Student f LEFT JOIN f.classTbls s WHERE s.ClassName = 'abc'
Because your Student entity has One To Many relationship with ClassTbl entity.
If you have entities A and B without any relation between them and there is strictly 0 or 1 B for each A, you could do:
select a, (select b from B b where b.joinProperty = a.joinProperty) from A a
This would give you an Object[]{a,b} for a single result or List<Object[]{a,b}> for multiple results.
Normally the ON clause comes from the mapping's join columns, but the JPA 2.1 draft allows for additional conditions in a new ON clause.
See,
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#ON
Please see :
public interface YourDBRepository extends JpaRepository<Employee, Long> {
#Query("select new com.mypackage.myDTO(dep.empCode, dep.empName, em.EmployeeCode, em.EmployeeName) \n" +
"from Department dep\n" +
"left join Employee em\n" +
"on dep.DepartmentCode = em.DepartmentCode") // this is JPQL so use classnames
List<myDTO> getDeptEmployeeList();
}
You can also use CrudRepository and include #JoinColumn with FK table class in PK table class and have List return list and then do find operation to achieve the same.
In Department entity class:
#OneToMany
#Fetch(FetchMode.JOIN)
#JoinColumn(name="DEPT_CODE")
private List<Employee> employees;
CriteriaBuilder is yet another option.

Categories