在 JPA Criteria API 的子查询中使用 ORDER BY 的替代方法是什么?

What are the alternatives to using an ORDER BY in a Subquery in the JPA Criteria API?

考虑以下两个表:

  1. Project ( id, project_name)
  2. Status ( id, id_project, status_name)

其中 Status 包含 Project 所处的所有状态。

假设我们要查询最新状态名称为 "new" 的所有项目。我提出的 Sql 查询是:

SELECT q.id_project FROM status q
WHERE q.status_name like 'new'
AND q.id IN (
    SELECT TOP 1 sq.id from status sq
    WHERE q.id_project = sq.id_project 
    ORDER BY sq.id DESC )

我正在尝试使用 Criteria API 复制上述查询,但我注意到 class CriteriaQuery has the method orderBy but the class Subquery 没有。

到目前为止我想出的Criteria Query是:

CriteriaQuery<Project> q = criteriaBuilder.createQuery(Project.class);
Root<Status> qFrom       = q.from(Status.class);
Subquery<Integer> sq     = q.subquery(Integer.class);
Root<Status> sqFrom      = sq.from(Status.class);

sq.select(sqFrom.get(Status_.id))
  .where(criteriaBuilder.equal(sqFrom.get(Status_.project), qFrom.get(Status_.project))

我卡在这里了,因为 Subquery sq 没有任何方法可以对其结果进行排序并只返回最新的结果。

为了在上述场景中获得所需的结果,对子查询进行排序的替代方法是什么?

正如您所说,您的子查询等同于

的 JPQL
SELECT MAX(sq.id) FROM status sq WHERE q.id_project = sq.id_project

JPA 规范应该完全支持它。 就标准 API 而言,我猜是这样

Subquery<Integer> sq     = q.subquery(Integer.class);
Root<Status> sqFrom      = sq.from(Status.class);
Expression maxExpr = criteriaBuilder.max(sqFrom.get(Status_.id));
sq.select(maxExpr).where(criteriaBuilder.equal(sqFrom.get(Status_.project), qFrom.get(Status_.project))

可以使用 SELECT MAX 而不是 SELECT TOP 1 ... ORDER BY ... 编写子查询:

SELECT q.id_project FROM status q
WHERE q.status_name like 'new'
AND q.id = (
    SELECT MAX(sq.id) from status sq
    WHERE q.id_project = sq.id_project)

这也会更快,因为与查找最大值相比,对所有记录进行排序速度较慢。正如已经发布的那样,它可以转换为 CriteriaQuery。

不幸的是,条件查询不支持在子查询中排序 - 请参阅这些相关答案:

如果您确实需要ORDER BY(例如,在下面的示例中允许一定范围的值),另一种方法是使用本机查询而不是 CriteriaQuery :

Query query = entityManager.createNativeQuery(
    "SELECT bar FROM foo WHERE bar IN (SELECT TOP 10 baz FROM quux ORDER BY quuux)");

除了其他答案之外,我认为数据库优化器无法将具有聚合函数的相关子查询隐式重写为不相关的形式,因此我认为显式编写不相关的子查询在性能方面更好(下面的示例使用 JPQL,Criteria API 等效项应该很简单):

select q.project from Status q
where q.name = 'new'
  and q.id in (select max(sq.id) from Status sq group by sq.project.id)