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

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

请考虑以下两个表:

  • Project ( id, project_name)
  • Status ( id, id_project, status_name)
  • 其中Status包含Project处于的所有状态。

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

    1
    2
    3
    4
    5
    6
    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复制以上查询,并且我注意到CriteriaQuery类具有方法orderBy,但Subquery类却没有。

    到目前为止,我提出的条件查询是:

    1
    2
    3
    4
    5
    6
    7
    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没有任何方法可以对结果进行排序并仅返回最新的结果。

    在上述情况下,对子查询进行排序以获取所需结果的替代方法是什么?


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

    1
    2
    3
    4
    5
    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。

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

    https://stackoverflow.com/questions/19549616#28233425
    https://stackoverflow.com/questions/5551459#5551571

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

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

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

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

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

    1
    2
    3
    4
    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))


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

    1
    2
    3
    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)