using subqueries in jpa criteria api

被刻印的时光 ゝ 提交于 2020-11-24 20:01:35

问题


I'm studying JPA criteria api and my database contains Employee table. I am trying to find all the employees who are paid second highest salary. I was able to write JPQL successfully as follows.

SELECT e FROM Employee e WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee) )

but now I am trying to convert it to criteria api and have tried following.

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
    Root<Employee> e1 = c.from(Employee.class);
    c.select(e1);

    Subquery<Number> sq = c.subquery(Number.class);
    Root<Employee> e2 = sq.from(Employee.class);
    sq.select(cb.max(e2.<Number> get("salary")));

    Subquery<Number> sq1 = sq.subquery(Number.class);
    Root<Employee> e3 = sq1.from(Employee.class);
    sq1.select(cb.max(e3.<Number> get("salary")));

    c.where(cb.lessThan(e2.<Number>get("salary"), e3.<Number>get("salary")));// error here
    c.where(cb.equal(e1.get("salary"), sq));

I get the error that parameters are not compatible with lessThan method. I do not understand how can I get this query worked out. Is my approach right?

EDIT :- Updating the question after Mikko's answer.

The jpql provided above provides following results, which are the employees with second highest salary.

Harish Taware salary 4000000.0
Nilesh Deshmukh salary 4000000.0
Deodatta Chousalkar salary 4000000.0
Deodatta Chousalkar salary 4000000.0

but the updated criteria query as below,

        CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
        Root<Employee> e1 = c.from(Employee.class);
        c.select(e1);

        Subquery<Long> sq = c.subquery(Long.class);
        Root<Employee> e2 = sq.from(Employee.class);
        sq.select(cb.max(e2.<Long> get("salary")));

        Subquery<Long> sq1 = sq.subquery(Long.class);
        Root<Employee> e3 = sq1.from(Employee.class);
        sq1.select(cb.max(e3.<Long> get("salary")));

        c.where(cb.lessThan(e2.<Long> get("salary"), e3.<Long> get("salary")));
        c.where(cb.equal(e1.get("salary"), sq));

        employees = em.createQuery(c).getResultList();

        for (Employee employee : employees) {
            System.out.println(employee.getName() + "salary"
                    + employee.getSalary());
        }

This provides the employee with highest salary. The result is as below.

Pranil Gildasalary5555555.0

Please tell me where I am being wrong. An explanation is deeply appreciated.


回答1:


After some more trial and error, I could write the query to select employees with second maximum salary. I would like to suggest that you should write a JPQL query first and write the criteria api accordingly. This is what I analyzed from JPQL.

SELECT e FROM Employee e 
WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp 
WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee) )

Now we can see that

  • There are 2 subqueries, i.e. subquery of main query contains another subquery
  • The identification variables e, emp and employee correspond to the main query, subquery of main query and subquery of subquery.
  • Now while comparing the result of subqueries i.e. maximum salary compared with the employee salary of outer query, the identification variable from outer query is used. for e.g. WHERE emp.salary = (SELECT MAX(emp.salary) FROM Employee emp)

Now let us convert this query in criteria api.

First write CriteriaQuery that corresponds to outermost query i.e. SELECT e FROM Employee e WHERE e.salary =

CriteriaQuery<Employee> c1 = cb.createQuery(Employee.class);
Root<Employee> e3 = c1.from(Employee.class);
c1.select(e3);

Let us leave the WHERE e.salary = for now and go for the subquery

Now this should have a subquery that selects the maximum salary of employees i.e. SELECT MAX(emp.salary) FROM Employee emp WHERE emp.salary < again let us leave the WHERE emp.salary < for now.

Subquery<Long> sq1 = c1.subquery(Long.class);
Root<Employee> e4 = sq1.from(Employee.class);
sq1.select(cb.max(e4.<Long> get("salary")));

repeating this for subquery of above subquery,

Subquery<Long> sq2 = sq1.subquery(Long.class);
Root<Employee> e5 = sq2.from(Employee.class);
sq2.select(cb.max(e5.<Long> get("salary")));

Now we have written subqueries but WHERE conditions need to be applied yet. So now the where condition in criteria api corresponding to WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee) will be as below.

sq1.where(cb.lessThan(e4.<Long> get("salary"), sq2));

Similarly, WHERE condition corresponding to WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp will be as below.

c1.where(cb.equal(e3.<Long> get("salary"), sq1));

So the complete query which gives the employees with second highest salary can be written in criteria api as below.

        CriteriaQuery<Employee> c1 = cb.createQuery(Employee.class);
    Root<Employee> e3 = c1.from(Employee.class);
    c1.select(e3);

    Subquery<Long> sq1 = c1.subquery(Long.class);
    Root<Employee> e4 = sq1.from(Employee.class);
    sq1.select(cb.max(e4.<Long> get("salary")));

    Subquery<Long> sq2 = sq1.subquery(Long.class);
    Root<Employee> e5 = sq2.from(Employee.class);
    sq2.select(cb.max(e5.<Long> get("salary")));

    sq1.where(cb.lessThan(e4.<Long> get("salary"), sq2));
    c1.where(cb.equal(e3.<Long> get("salary"), sq1));

    employees = em.createQuery(c1).getResultList();

    for (Employee employee : employees) {
        System.out.println(employee.getName() + " " + employee.getSalary());
    }



回答2:


As documented, it cannot work because Number is not Comparable:

<Y extends java.lang.Comparable<? super Y>> Predicate lessThan(Expression<? extends Y> x,
                                                               Expression<? extends Y> y)

For expression with Number there is method Criteriabuilder.lt that takes such arguments:

c.where(cb.lt(e2.<Number>get("salary"), e3.<Number>get("salary")));

Other option is to change type argument from Number to something more specific. If salary is Long, following should work:

Subquery<Long> sq = c.subquery(Long.class);
Root<Employee> e2 = sq.from(Employee.class);
sq.select(cb.max(e2.<Long> get("salary")));

Subquery<Long> sq1 = sq.subquery(Long.class);
Root<Employee> e3 = sq1.from(Employee.class);
sq1.select(cb.max(e3.<Long> get("salary")));

c.where(cb.lessThan(e2.<Long>get("salary"), e3.<Long>get("salary")));
c.where(cb.equal(e1.get("salary"), sq));


来源:https://stackoverflow.com/questions/25163443/using-subqueries-in-jpa-criteria-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!