Subquery SELECT as a field using JPA Criteria API - java

I have the following SQL query:
select
A.A_ID,
B.Lib,
A.Lib,
C.Lib,
(SELECT count(*) FROM X WHERE A.A_ID = X.A_ID) AS countX,
(SELECT count(*) FROM Y WHERE A.A_ID = Y.A_ID) AS countY,
(SELECT count(*) FROM Z WHERE A.A_ID = Z.A_ID) AS countZ
from
A
left outer join
C
on A.C_ID=C.C_ID
left outer join
B
on A.B_ID=B.B_ID;
I want to create this query using the JPA Criteria API, so I did as following :
final CriteriaBuilder builder = getCriteriaBuilder();
final CriteriaQuery<A_DTO> criteriaQuery = builder.createQuery(A_DTO.class);
final Root<A> aRoot = criteriaQuery.from(A.class);
// LEFT OUTER JOIN B
Join<A, B> bJoin = aRoot.join(A_.bID, JoinType.LEFT);
// LEFT OUTER JOIN C
Join<A, C> cJoin = aRoot.join(A_.cID, JoinType.LEFT);
// (SELECT count(*) FROM X WHERE A.A_ID = X.A_ID) AS countX
final Subquery<X> xSubquery = criteriaQuery.subquery(X.class);
final Root<X> xRoot = xSubquery.from(X.class);
xRoot.alias("countX");
xSubquery.select(xRoot);
xSubquery.where(builder.equal(xRoot.get(X_.a).get(A_.aID), aRoot.get(A_.aID)));
// (SELECT count(*) FROM Y WHERE A.A_ID = Y.A_ID) AS countY
final Subquery<Y> ySubquery = criteriaQuery.subquery(Y.class);
final Root<Y> yRoot = ySubquery.from(Y.class);
yRoot.alias("countY");
ySubquery.select(yRoot);
ySubquery.where(builder.equal(yRoot.get(Y_.a).get(A_.aID), aRoot.get(A_.aID)));
// (SELECT count(*) FROM Z WHERE A.A_ID = Z.A_ID) AS countZ
final Subquery<Z> zSubquery = criteriaQuery.subquery(Z.class);
final Root<Z> zRoot = zSubquery.from(Z.class);
zRoot.alias("countZ");
zSubquery.select(zRoot);
zSubquery.where(builder.equal(zRoot.get(Z_.a).get(A_.aID), aRoot.get(A_.aID)));
// Selection
criteriaQuery.multiselect(aRoot.get(A_.aID),
bJoin.get(B_.lib),
aRoot.get(A_.lib),
cJoin.get(C_.lib),
builder.count(xRoot),
builder.count(yRoot),
builder.count(zRoot));
return getEntityManager().createQuery(criteriaQuery);
But this didn't work for me, and this generates the following SQL query instead:
select
a0_.aID as col_0_0_,
b2_.Lib as col_1_0_,
a0_.Lib as col_3_0_,
c1_.Lib as col_4_0_,
count(countX) as col_5_0_,
count(countY) as col_6_0_,
count(countZ) as col_7_0_
from
A a0_
left outer join
C c1_
on a0_.cID=c1_.cID
left outer join
B b2_
on a0_.bID=b2_.bID;
Which will throw the following SQL exception:
WARN o.h.e.j.s.SqlExceptionHelper - SQL Error: 904, SQLState: 42000
ERROR o.h.e.j.s.SqlExceptionHelper - ORA-00904: "countZ": invalid
identifier
How can I solve this issue ?
Edit:
I have resolved this using the following code :
final CriteriaBuilder builder = getCriteriaBuilder();
final CriteriaQuery<A_DTO> criteriaQuery = builder.createQuery(A_DTO.class);
final Root<A> aRoot = criteriaQuery.from(A.class);
// LEFT OUTER JOIN B
Join<A, B> bJoin = aRoot.join(A_.bID, JoinType.LEFT);
// LEFT OUTER JOIN C
Join<A, C> cJoin = aRoot.join(A_.cID, JoinType.LEFT);
// (SELECT count(*) FROM X WHERE A.A_ID = X.A_ID) AS countX
final Subquery<Long> xSubquery = criteriaQuery.subquery(Long.class);
final Root<X> xRoot = xSubquery.from(X.class);
final Expression<Long> xCount = builder.count(xRoot);
xSubquery.select(xCount);
xSubquery.where(builder.equal(xRoot.get(X_.a).get(A_.aID), aRoot.get(A_.aID)));
// (SELECT count(*) FROM Y WHERE A.A_ID = Y.A_ID) AS countY
final Subquery<Long> ySubquery = criteriaQuery.subquery(Long.class);
final Root<Y> yRoot = ySubquery.from(Y.class);
final Expression<Long> yCount = builder.count(yRoot);
ySubquery.select(yCount);
ySubquery.where(builder.equal(yRoot.get(Y_.a).get(A_.aID), aRoot.get(A_.aID)));
// (SELECT count(*) FROM Z WHERE A.A_ID = Z.A_ID) AS countZ
final Subquery<Long> zSubquery = criteriaQuery.subquery(Long.class);
final Root<Z> zRoot = zSubquery.from(Z.class);
final Expression<Long> zCount = builder.count(zRoot);
zSubquery.select(zCount);
zSubquery.where(builder.equal(zRoot.get(Z_.a).get(A_.aID), aRoot.get(A_.aID)));
// Selection
criteriaQuery.multiselect(aRoot.get(A_.aID),
bJoin.get(B_.lib),
aRoot.get(A_.lib),
cJoin.get(C_.lib),
xSubquery.getSelection(),
ySubquery.getSelection(),
zSubquery.getSelection());
return getEntityManager().createQuery(criteriaQuery);
But when I wanted to select results depending on the count value or sort the result using it, I had to change the query as following:
WITH cte AS ( select
A.A_ID,
B.Lib,
A.Lib,
C.Lib,
(SELECT count(*) FROM X WHERE A.A_ID = X.A_ID) AS countX,
(SELECT count(*) FROM Y WHERE A.A_ID = Y.A_ID) AS countY,
(SELECT count(*) FROM Z WHERE A.A_ID = Z.A_ID) AS countZ
from
A
left outer join
C
on A.C_ID=C.C_ID
left outer join
B
on A.B_ID=B.B_ID)
SELECT * FROM cte
WHERE countX > 2
ORDER BY countY, countZ DESC;
Now I have no idea how to create this Common Table Expression in JPA Criteria API.

Related

SQL Server query no longer slow after query char or parameter type change

I have the following query :
SELECT
A.id,
NEWID() as id,
RTRIM(LTRIM(A.label)) as label
FROM (
SELECT
a.id_aff as id,
p.nom as label
FROM
A a inner join
P p on p.id_aff = a.id_aff INNER JOIN
L l on l.id_aff = a.id_aff INNER JOIN
PL pl on pl.id_lien = l.id_lien AND pl.id_prog = p.id_prog INNER JOIN
S s on s.id_prog = p.id_prog
where
s.id_pub = :idPub and l.id_type = :idType and
s.active = 1 and s.valid = 1
UNION
SELECT
a.id_aff as id,
p.nom as label
FROM
A a inner join
P p on p.id_aff = a.id_aff INNER JOIN
L l on l.id_aff = a.id_aff INNER JOIN
S s on s.id_prog = p.id_prog INNER JOIN
SL sl on sl.id_lien = l.id_lien AND sl.id_session = p.id_prog
where
s.id_pub = :idPub and l.id_type = :idType and
s.active = 1 and s.valid = 1
) A
ORDER BY
RTRIM(LTRIM(A.label)) ASC
Normally the query execution time is approximately 2 - 3 seconds.
Suddenly this query execution time rise to 1 minute - 1 minute and 30 seconds.
This query is executed by hibernate by the following code :
Query query = entityManager.createNativeQuery(QUERY, Select.class);
Integer id = 1;
query.setParameter("id", idAffilie);
query.setParameter("idType", SystemConstant.ID_TYPE);
List<Select> resultList = query.getResultList();
I execute the following tests :
1) Changing the parameter type
If for example i change the type of id from Integer to String without modifying the query, the query return the same result and the execution the is back to normal (2 - 3 seconds) :
Query query = entityManager.createNativeQuery(QUERY, Select.class);
String id = "1";
query.setParameter("id", idAffilie);
query.setParameter("idType", SystemConstant.ID_TYPE);
List<Select> resultList = query.getResultList();
2) Changing the query formatting
If i change the query formatting the same result occur the query execution the is back to normal (2 - 3 seconds). In this example, I change the SELECT formatting :
SELECT A.id, NEWID() as id, RTRIM(LTRIM(A.label)) as label
FROM (
SELECT
a.id_aff as id,
p.nom as label
FROM
A a inner join
P p on p.id_aff = a.id_aff INNER JOIN
L l on l.id_aff = a.id_aff INNER JOIN
PL pl on pl.id_lien = l.id_lien AND pl.id_prog = p.id_prog INNER JOIN
S s on s.id_prog = p.id_prog
where
s.id_pub = :idPub and l.id_type = :idType and
s.active = 1 and s.valid = 1
UNION
SELECT
a.id_aff as id,
p.nom as label
FROM
A a inner join
P p on p.id_aff = a.id_aff INNER JOIN
L l on l.id_aff = a.id_aff INNER JOIN
S s on s.id_prog = p.id_prog INNER JOIN
SL sl on sl.id_lien = l.id_lien AND sl.id_session = p.id_prog
where
s.id_pub = :idPub and l.id_type = :idType and
s.active = 1 and s.valid = 1
) A
ORDER BY
RTRIM(LTRIM(A.label)) ASC
Question : why changing a parameter or the query formatting makes this query execution time back to the normal ?
This issue is encountered with a few queries but not all of them.
Cardinalities of tables :
A : 9000 records
P : 25000 records
L : 185000 records
PL : 736000 records
S : 600000 records
SL : 20000 records
SQL Server : Microsoft SQL Server 2019 (RTM-CU3) (KB4538853) - 15.0.4023.6 (X64)
Spring boot : 2.0.9
Spring data JPA : 2.0.14
Hibernate : 5.2.18
Thanks and have a good day.
EDIT
SQL server driver maven version : 9.2.1.jre8

Select from a select statement using JPA Criteria API

I have the following sql statement :
select * from (select A.A_ID, (SELECT count(*) FROM X WHERE A.A_ID = X.A_ID) AS countX from A) where countX > 3 order by countX ;
How can I create this using JPA Criteria API ?
I know how to create the inner sql query it's just the outer one I couldn't figure out, this is how I created the inner one :
final CriteriaBuilder builder = getCriteriaBuilder();
final CriteriaQuery<A_DTO> criteriaQuery = builder.createQuery(A_DTO.class);
final Root<A> aRoot = criteriaQuery.from(A.class);
final Subquery<Long> xSubquery = criteriaQuery.subquery(Long.class);
final Root<X> xRoot = xSubquery.from(X.class);
final Expression<Long> xCount = builder.count(xRoot);
xSubquery.select(xCount);
xSubquery.where(builder.equal(xRoot.get(X_.a).get(A_.aID), aRoot.get(A_.aID)));
criteriaQuery.multiselect(aRoot.get(A_.aID), xSubquery.getSelection());
return getEntityManager().createQuery(criteriaQuery);

Criteria API count from grouped count

I have a problem with Criteria API (JPA 2.0). I want to write code which returns the same result as query (select count from (select count from ... group by ...)):
SELECT COUNT(*) FROM (
SELECT COUNT(*) FROM a
LEFT JOIN p ON a.id = p.a_id
LEFT JOIN s ON a.id = s.a_id
WHERE ... GROUP BY s.r_id
)
I wrote something like this:
CriteriaBuilder builder = slave.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Subquery<Long> subquery = query.subquery(Long.class);
Root<A> entity = subquery.from(A.class);
Join<A, P> pJoin = entity.join("p", JoinType.LEFT);
Join<A, S> sJoin = entity.join("s", JoinType.LEFT);
subquery.select(builder.count(entity));
subquery.where(/*where predicate*/);
subquery.groupBy(sJoin.get("r"));
query.select(builder.count(subquery));
Long result = slave.createQuery(query).getSingleResult();
But I get the exception:
java.lang.IllegalStateException: No criteria query roots were specified
What am I doing wrong here?

HSQLDB HAVING object not found

Hi there I am having trouble bringing a mysql query in hsqldb to work for a unit test.
Unfort I can't show you the real code.
I tried to make it as simple as possible:
SELECT A.id,
(SELECT count(*) FROM SomeOtherTable) as myAlias
FROM Orders A WHERE A.someKz = 2 HAVING 1 = myAlias;
Will result in
user lacks privilege or object not found: MYALIAS
So my question is, how to access the variable myAlias in the HAVING clause?
This query might not look yousefull to you but it is very simplified. For Example there are serveral complex Subselects with variables.
UPDATE
This one has basically the same problem
SELECT A.id, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.refNR > 0) as anz1, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id ) as anz2, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.SID = 18 ) as anz3 FROM ABable A WHERE
A.someInt IN(1,2)
AND A.someString > '20150308190127'
AND(SELECT Count(*) FROM DTable D WHERE D.Aid = A.id ) = 0 HAVING anz1 = anz2 AND anz3 < anz2 ORDER BY someString ASC LIMIT 1;
Your example can be written as this for HSQLDB. It returns a result only if the Orders table contains a single row. But if the 1 is actually another expression in the SELECT list, you need to write it out:
SELECT A.id, 1 as myAlias
FROM Orders A WHERE A.someKz = 2 AND (SELECT count(*) FROM Orders) = 1
SELECT A.id, A.acol * 12 as myAlias
FROM Orders A WHERE A.someKz = 2 AND (SELECT count(*) FROM Orders) = A.acol *12
Your full query is far more complex. You should therefore write it with a wrapper SELECT statement:
SELECT * FROM (
SELECT A.id, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.refNR > 0) as anz1, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id ) as anz2, (SELECT count(*) as anzahl1 FROM ABTable p WHERE p.Aid = A.id AND p.SID = 18 ) as anz3 FROM ABable A
WHERE A.someInt IN(1,2)
AND A.someString > '20150308190127'
AND(SELECT Count(*) FROM DTable D WHERE D.Aid = A.id ) = 0
) WHERE anz1 = anz2 AND anz3 < anz2 ORDER BY someString ASC LIMIT 1;

JPA 2 + Criteria API

Employee (table)
id - int
ctd_id - int
message - char
SELECT a.*
FROM Employee a left outer join
( select * from Employee where message = 23 ) b
on a.ctd_id = b.ctd_id
where a.message = 22 and b.id is null;
This is what i tried
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> criteria = cb.createQuery(Employee.class);
Root<Employee> emp = criteria.from(Employee.class);
CriteriaQuery<Employee> sq = c.select(emp);
Subquery<Employee> sq2 = criteria.subquery(Employee.class);
Root<Employee> emp2 = sq2.from(Employee.class);
Join<Employee,Employee> sqEmp = emp2.join("ctd_id", JoinType.LEFT);
sq.select(sqemp).where(cb.equal(emp2.get("message"), cb.parameter(String.class, "23")));
sq.where(cb.in(path).value(sq2));
TypedQuery<Employee> q = em.createQuery(criteria);
List<Employee> employeess = q.getResultList()
But, i am not able to understand as to how i should apply a join on a subquery with where clause.
please help .
JPA does not support sub-queries in the FROM clause.
Either use SQL for your query, or rewrite it not to have a sub-query in the from clause, it doesn't look like you need it.

Categories

Resources