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

Posted

技术标签:

【中文标题】在 JPA Criteria API 的子查询中使用 ORDER BY 的替代方法是啥?【英文标题】:What are the alternatives to using an ORDER BY in a Subquery in the JPA Criteria API?在 JPA Criteria API 的子查询中使用 ORDER BY 的替代方法是什么? 【发布时间】:2017-05-05 14:05:41 【问题描述】:

考虑以下两个表:

    Project ( id, project_name) Status ( id, id_project, status_name)

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

假设我们要查询最新状态名称为“新”的所有项目。我想出的 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 复制上述查询,我​​注意到 CriteriaQuery 类具有 orderBy 方法,但 Subquery 类没有。

到目前为止我提出的标准查询是:

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 不允许在子查询中使用 ORDER BY,而 Criteria 只是反映了这一点。 “子查询 ::= simple_select_clause subquery_from_clause [where_clause] [groupby_clause] [having_clause]” @NeilStockton 我明白这一点,这就是我要求替代解决方案的原因 所以你想从子查询中返回“sq.id”的最大值(受 where 子句的约束)?你不能只做一个“SELECT MAX(sq.id) FROM status sq WHERE q.id_project = sq.id_project”的子查询吗? @NeilStockton 你是对的。不知道为什么我没有想到这一点。请将其发布为答案,我会接受。 【参考方案1】:

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

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

JPA 规范应该完全支持它。 就 Criteria 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))

【讨论】:

【参考方案2】:

可以使用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。

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

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

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

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

【讨论】:

【参考方案3】:

除了其他答案之外,我不认为数据库优化器能够将具有聚合函数的相关子查询隐式重写为不相关的形式,因此我认为显式编写不相关的子查询在性能方面更好(下面的示例使用 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)

【讨论】:

以上是关于在 JPA Criteria API 的子查询中使用 ORDER BY 的替代方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

比较 JPA Criteria API 中的日期实体

JPA Criteria builder IN 子句查询

使用 JPA 的 Criteria API 按日期间隔分组

如何使用 JPA Criteria API 连接不相关的实体

jpa criteria-api:加入子选择

JPA Criteria API where subclass - 出现错误:无法针对路径 [null] 解析属性 [lastName]