Выберите количество вхождений каждого статуса в другой таблице, используя критерии jpa - PullRequest
1 голос
/ 24 марта 2019

Мне нужно выбрать основную таблицу и количество вхождений каждого статуса в другой таблице, используя критерии api только в одном запросе.

Мое текущее решение - это собственный запрос, который работает, но я хочу сделать это более объектно-ориентированным способом. Я попытался сделать это в критериях, используя определенный запрос, просто чтобы выбрать все состояния, а затем посчитать его вручную. Но при таком подходе я вызываю два запроса: один для извлечения подробностей из моей основной таблицы, а другой для выбора всех состояний, где идентификатор совпадает с основной таблицей. Есть ли более эффективный способ сделать это?

Вот мой родной запрос (упрощенный):

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
}

1 Ответ

0 голосов
/ 24 марта 2019

Ваш запрос может быть переписан без объединения подзапросов:

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;

(в противном случае вам потребуется RIGHT JOIN от CmParam до Cm, что-то, что 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...