【问题标题】:Select count of occurrences of each status in another table using jpa criteria使用 jpa 标准选择另一个表中每个状态的出现次数
【发布时间】:2019-03-24 09:56:55
【问题描述】:

我需要在一个查询中使用标准 api 选择我的主表和另一个表中每个状态的出现次数。

我当前的解决方案是原生查询,它正在工作,但我想以更基于对象的方式进行。 我尝试通过使用特定查询来选择所有状态,然后手动计算它,从而在标准中执行此操作。但是通过这种方法,我调用了两个查询:一个用于获取主表中的详细信息,另一个用于选择 id 与主表相同的所有状态。 有没有更有效的方法来做到这一点?

这是我的原生查询(简化):

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
}

【问题讨论】:

    标签: java jpa spring-data-jpa criteria-api


    【解决方案1】:

    您的查询可以在没有子查询连接的情况下重写:

    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;
    

    (否则,您需要从 CmParamCmRIGHT JOIN,这是 Hibernate 不支持的)

    然后条件查询变为:

    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&lt;CmParam&gt; b = cq.from(CmParam.class)Join&lt;CmParam, Cm&gt; a = b.join("cm")
    • 如果在您的实际查询中,您从Cm 中选择的属性不仅仅是cmIdstatus,您可能还需要在groupBy 中列出它们

    【讨论】:

    • 谢谢!!尤其是查询重写。我不知道为什么我对那个子查询选择了困难的方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-17
    • 2018-11-28
    相关资源
    最近更新 更多