在jpa标准api中使用子查询

时间:2014-08-06 14:45:45

标签: jpa-2.0 criteria-api

我正在学习JPA标准api,我的数据库包含Employee表。 我试图找到所有薪水第二高的员工。我能够成功编写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) )

但现在我正在尝试将其转换为条件api并尝试过以下操作。

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));

我收到参数与lessThan方法不兼容的错误。我不明白如何才能解决这个问题。我的做法是对的吗?

编辑: - 在Mikko回答后更新问题。

上面提供的jpql提供了以下结果,即薪水第二高的员工。

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

但更新的条件查询如下,

        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());
        }

这为员工提供最高薪水。结果如下。

Pranil Gildasalary5555555.0

请告诉我我错在哪里。非常感谢您的解释。

2 个答案:

答案 0 :(得分:7)

经过多次试验和错误后,我可以编写查询来选择具有第二个最高薪水的员工。我想建议您首先编写一个JPQL查询并相应地编写标准api。这是我从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) )

现在我们可以看到

  • 有2个子查询,即主查询的子查询包含另一个子查询
  • 标识变量e,emp和employee对应于主查询,主查询的子查询和子查询的子查询。
  • 现在在比较子查询的结果(即最大工资与外部查询的员工工资相比)时,使用来自外部查询的标识变量。例如WHERE emp.salary = (SELECT MAX(emp.salary) FROM Employee emp)

现在让我们在条件api中转换此查询。

首先编写与最外层查询对应的CriteriaQuery,即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);

让我们暂时离开WHERE e.salary =并转到子查询

现在这应该有一个子查询,选择员工的最高工资,即SELECT MAX(emp.salary) FROM Employee emp WHERE emp.salary <,让我们暂时离开WHERE emp.salary <

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")));

现在我们已经编写了子查询,但还需要应用WHERE条件。那么现在对应于WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee)的标准api中的where条件如下所示。

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

类似地,对应于WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp的WHERE条件如下。

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

因此,给员工第二高薪的完整查询可以用下面的标准api编写。

        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());
    }

答案 1 :(得分:1)

作为documented,它无效,因为Number不是Comparable

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

对于带有Number的表达式,方法Criteriabuilder.lt采用了这样的参数:

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

其他选项是将类型参数从Number更改为更具体的更新。如果薪水很长,以下工作应该有效:

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));