Select 每个状态在另一个 table 使用 jpa 标准的出现次数
Select count of occurrences of each status in another table using jpa criteria
我需要 select 我的主要 table 和每个状态在另一个 table 中的出现次数,仅在一个查询中使用条件 api。
我当前的解决方案是使用本机查询,这很有效,但我想以更基于对象的方式来实现。
我尝试通过使用特定查询来在标准中做到这一点 select 所有状态,然后手动对其进行计数。但是通过这种方法,我调用了两个查询:一个是在我的主 table 中获取详细信息,另一个是 select id 与主 table 相同的所有状态。
有没有更有效的方法来做到这一点?
这是我的本机查询(简化):
SELECT * FROM(
SELECT a.id, a.type, b.count_pending, b.count_failed, b.count_processed
FROM CM AS a
LEFT JOIN ( SELECT
COUNT( CASE WHEN status = 'PENDING' THEN 1 ELSE NULL END ) count_pending,
COUNT( CASE WHEN status = 'FAILED' THEN 1 ELSE NULL END ) count_failed,
COUNT( CASE WHEN status = 'PROCESSED' THEN 1 ELSE NULL END ) count_processed
FROM CM_PARAM WHERE id_cm = :cmId
GROUP BY id_cm
) AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = :cmId) AS a
这是我的 CM 实体(简体):
@Entity
public class Cm {
@Id
private Long idCm;
private String type;
// other fields
// setters and getters
}
这是我的 CM_PARAM 实体(简体):
@Entity
public class CmParam {
@Id
private Long idCmp;
@ManyToOne
@JoinColumn(name = "id_cm")
private Cm cm;
private String status;
// other fields
// setters and getters
}
使用本机查询方法,我可以在我的 Cm 实体中添加临时字段:
@Transient
private Long countPending;
@Transient
private Long countFailed;
@Transient
private Long countProcessed;
我如何使用标准 api 来做到这一点,如果可能的话,只需一次交易。
预期的输出是这样的:
{
"idCm": 1,
"type": "sms",
"countPending": 5,
"countFailed": 3,
"countProcessed": 9
}
您的查询可以在没有子查询连接的情况下重写:
SELECT
a.id_cm,
a.type
COUNT(CASE WHEN b.status = 'PENDING' THEN 1 ELSE NULL END) countPending,
COUNT(CASE WHEN b.status = 'FAILED' THEN 1 ELSE NULL END) countFailed,
COUNT( CASE WHEN b.status = 'PROCESSED' THEN 1 ELSE NULL END ) countProcessed
FROM CM AS a
LEFT JOIN CM_PARAM AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = ?1
GROUP BY a.id_cm, a.type
您必须将关联的反面添加到 Cm
:
@OneToMany(mappedBy = "cm")
private Set<CmParam> params;
(否则,您需要从 CmParam
到 Cm
的 RIGHT JOIN
,Hibernate 不支持)
Criteria 查询将变为:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<? extends Object[]> cq = cb.createQuery(new Object[0].getClass());
Root<Cm> a = cq.from(Cm.class);
Join<Cm, CmParam> b = a.join("params", JoinType.LEFT);
cq.where(cb.equal(a.get("idCm"), cb.parameter(Long.class, "idCm")));
cq.groupBy(a.get("idCm"), a.get("type"));
cq.multiselect(
a.get("idCm"),
a.get("type"),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "PENDING"), 1L)
.otherwise(cb.nullLiteral(Long.class))),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "FAILED"), 1L)
.otherwise(cb.nullLiteral(Long.class))),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "PROCESSED"), 1L)
.otherwise(cb.nullLiteral(Long.class))));
请注意,结果的类型为 Object[]
。如果您想对瞬态字段使用当前方法,最简单的方法是将适当的构造函数添加到 Cm
并使用 cb.construct()
方法:
cq.select(cb.construct(Cm.class, a.get("idCm"), a.get("type"), ...))
注意:
- 如果您不想将
params
字段添加到 Cm
,但您可以使用 INNER JOIN
,您可以只使用 Root<CmParam> b = cq.from(CmParam.class)
和 Join<CmParam, Cm> a = b.join("cm")
代替。
- 如果在您的实际查询中您从
Cm
中选择的属性不仅仅是 cmId
和 status
,您可能需要在 groupBy
中列出所有属性还有
我需要 select 我的主要 table 和每个状态在另一个 table 中的出现次数,仅在一个查询中使用条件 api。
我当前的解决方案是使用本机查询,这很有效,但我想以更基于对象的方式来实现。 我尝试通过使用特定查询来在标准中做到这一点 select 所有状态,然后手动对其进行计数。但是通过这种方法,我调用了两个查询:一个是在我的主 table 中获取详细信息,另一个是 select id 与主 table 相同的所有状态。 有没有更有效的方法来做到这一点?
这是我的本机查询(简化):
SELECT * FROM(
SELECT a.id, a.type, b.count_pending, b.count_failed, b.count_processed
FROM CM AS a
LEFT JOIN ( SELECT
COUNT( CASE WHEN status = 'PENDING' THEN 1 ELSE NULL END ) count_pending,
COUNT( CASE WHEN status = 'FAILED' THEN 1 ELSE NULL END ) count_failed,
COUNT( CASE WHEN status = 'PROCESSED' THEN 1 ELSE NULL END ) count_processed
FROM CM_PARAM WHERE id_cm = :cmId
GROUP BY id_cm
) AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = :cmId) AS a
这是我的 CM 实体(简体):
@Entity
public class Cm {
@Id
private Long idCm;
private String type;
// other fields
// setters and getters
}
这是我的 CM_PARAM 实体(简体):
@Entity
public class CmParam {
@Id
private Long idCmp;
@ManyToOne
@JoinColumn(name = "id_cm")
private Cm cm;
private String status;
// other fields
// setters and getters
}
使用本机查询方法,我可以在我的 Cm 实体中添加临时字段:
@Transient
private Long countPending;
@Transient
private Long countFailed;
@Transient
private Long countProcessed;
我如何使用标准 api 来做到这一点,如果可能的话,只需一次交易。
预期的输出是这样的:
{
"idCm": 1,
"type": "sms",
"countPending": 5,
"countFailed": 3,
"countProcessed": 9
}
您的查询可以在没有子查询连接的情况下重写:
SELECT
a.id_cm,
a.type
COUNT(CASE WHEN b.status = 'PENDING' THEN 1 ELSE NULL END) countPending,
COUNT(CASE WHEN b.status = 'FAILED' THEN 1 ELSE NULL END) countFailed,
COUNT( CASE WHEN b.status = 'PROCESSED' THEN 1 ELSE NULL END ) countProcessed
FROM CM AS a
LEFT JOIN CM_PARAM AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = ?1
GROUP BY a.id_cm, a.type
您必须将关联的反面添加到 Cm
:
@OneToMany(mappedBy = "cm")
private Set<CmParam> params;
(否则,您需要从 CmParam
到 Cm
的 RIGHT JOIN
,Hibernate 不支持)
Criteria 查询将变为:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<? extends Object[]> cq = cb.createQuery(new Object[0].getClass());
Root<Cm> a = cq.from(Cm.class);
Join<Cm, CmParam> b = a.join("params", JoinType.LEFT);
cq.where(cb.equal(a.get("idCm"), cb.parameter(Long.class, "idCm")));
cq.groupBy(a.get("idCm"), a.get("type"));
cq.multiselect(
a.get("idCm"),
a.get("type"),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "PENDING"), 1L)
.otherwise(cb.nullLiteral(Long.class))),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "FAILED"), 1L)
.otherwise(cb.nullLiteral(Long.class))),
cb.count(cb.selectCase()
.when(cb.equal(b.get("status"), "PROCESSED"), 1L)
.otherwise(cb.nullLiteral(Long.class))));
请注意,结果的类型为 Object[]
。如果您想对瞬态字段使用当前方法,最简单的方法是将适当的构造函数添加到 Cm
并使用 cb.construct()
方法:
cq.select(cb.construct(Cm.class, a.get("idCm"), a.get("type"), ...))
注意:
- 如果您不想将
params
字段添加到Cm
,但您可以使用INNER JOIN
,您可以只使用Root<CmParam> b = cq.from(CmParam.class)
和Join<CmParam, Cm> a = b.join("cm")
代替。 - 如果在您的实际查询中您从
Cm
中选择的属性不仅仅是cmId
和status
,您可能需要在groupBy
中列出所有属性还有