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);
Related
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.
I need to build following query using JPA and criteria query but I stuck on join conditions. The query is:
SELECT p.*
FROM output cu
JOIN user ur ON cu.id = ur.id AND cu.key = ur.key
JOIN product p ON cu.id = p.id AND cu.key = p.key
WHERE p.refreshtimestamp IS NOT NULL AND cu.active = true
So far I have following, but how to apply join conditions:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Output> cq = cb.createQuery(Output.class);
Root<Output> output= cq.from(Output.class);
Join<Output, User> user = output.join("user", JoinType.INNER);
Join<User, Product> product = user.join("product", JoinType.INNER);
Any help will be appreciated
Following should help.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> criteria = builder.createQuery(Product.class);
Root<Output> outputRoot = criteria.from(Output.class);
Root<User> userRoot = criteria.from(User.class);
Root<Product> productRoot = criteria.from(Product.class);
Predicate[] predictes = {
builder.equal(outputRoot.get("id"), userRoot.get("key")),
builder.equal(productRoot.get("id"), userRoot.get("key")),
builder.notEqual(productRoot.get("refreshtimestamp"), null), // not sure about null
builder.equal(outputRoot.get("active"), true)
};
criteria.select(productRoot);
criteria.where(builder.and(predicates));
Although this would produce cross joins query, it will work because of where clause making it work like inner join as you require.
I have the following native SQL query that I am trying to convert to JPA criteria:
select et.* from t_empl_tx et, t_dept d
where et.assigned_dept = d.dept (+)
and et.employee_id = :employee_id
and (et.start_date >= d.dept_est_date and
et.start_date <= d.dept_close_date or
et.start_date is null or
d.dept is null)
(Note that (+) is roughly equivalent to a left outer join in this case. Yes, I know it denotes the OPTIONAL table, etc, etc).
Here is my attempt at the code:
EntityManager entityManager = getEntityManager();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeTransaction> criteriaQuery = criteriaBuilder.createQuery(EmployeeTransaction.class);
Root<EmployeeTransaction> root = criteriaQuery.from(EmployeeTransaction.class);
// this line bombs!
Join<EmployeeTransaction, Department> join =
root.join(EmployeeTransaction_.assignedDepartment).join(Department_.id).join(DepartmentCompositeId_.department, JoinType.LEFT);
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get(EmployeeTransaction_.id).get(EmployeeTransactionCompositeId_.employeeId), employeeId));
predicates.add(criteriaBuilder.or(
criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(root.<Date>get(EmployeeTransaction_.requestedStartDate), join.get(Department_.id).<Date>get(DepartmentCompositeId_.departmentCreationDate)),
criteriaBuilder.lessThanOrEqualTo(root.<Date>get(EmployeeTransaction_.requestedStartDate), join.<Date>get(Department_.departmentCloseDate))
),
criteriaBuilder.isNull(root.get(EmployeeTransaction_.requestedStartDate)),
criteriaBuilder.isNull(join.get(Department_.id).get(DepartmentCompositeId_.departmentCreationDate))
));
criteriaQuery.select(root).where(predicates.toArray(new Predicate[]{}));
TypedQuery<EmployeeTransaction> query = entityManager.createQuery(criteriaQuery);
List<EmployeeTransaction> result = query.getResultList();
This issue seems to be that I'm trying to join a string column, assigedDepartment, to a single field of a composite ID. This is perfectly legal in SQL, but not so easy in the code.
One option is to convert to a number of subqueries, which seems to kill the point of the left outer join entirely.
Can anyone point out what I'm doing wrong?
Jason
You should post your entities so that the answers can be more specific.
However, I'll give a try.
If I am right, you can rewrite the query:
select et.*
from t_empl_tx et
left join t_dept d on et.assigned_dept = d.dept
where
et.employee_id = :employee_id
and (
et.start_date >= d.dept_est_date
and et.start_date <= d.dept_close_date
or et.start_date is null
or d.dept is null)
So, shortly, you have to move the JoinType.LEFT to assignedDepartment join:
EntityManager entityManager = getEntityManager();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeTransaction> criteriaQuery = criteriaBuilder.createQuery(EmployeeTransaction.class);
Root<EmployeeTransaction> root = criteriaQuery.from(EmployeeTransaction.class);
Join<EmployeeTransaction, Department> department = root.join(EmployeeTransaction_.assignedDepartment, JoinType.LEFT);
Path<Date> employeeTransactionRequestedStartDate = root.get(EmployeeTransaction_.requestedStartDate);
Path<DepartmentCompositeId> departmentId = department.get(Department_.id);
Path<Date> departmentCreationDate = departmentId.get(DepartmentCompositeId_.departmentCreationDate)
Path<Date> departmentCloseDate = departmentId.get(DepartmentCompositeId_.departmentCloseDate)
criteriaQuery.select(root).where(
criteriaBuilder.equal(root.get(EmployeeTransaction_.id).get(EmployeeTransactionCompositeId_.employeeId), employeeId),
criteriaBuilder.or(
criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(employeeTransactionRequestedStartDate, departmentCreationDate)),
criteriaBuilder.lessThanOrEqualTo(employeeTransactionRequestedStartDate, departmentCloseDate)
),
criteriaBuilder.isNull(employeeTransactionRequestedStartDate),
criteriaBuilder.isNull(departmentCreationDate)
)
);
TypedQuery<EmployeeTransaction> query = entityManager.createQuery(criteriaQuery);
List<EmployeeTransaction> result = query.getResultList();
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?
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.