我如何使用JPA标准API来执行以下操作:
select count(distinct column1, column2) from table
使用CriteriaBuilder.countDistinct在一列/路径上执行此操作很简单,但如何在两个路径/列上执行此任务?
这是一个迟到的答案:-),但我不确定情况是否发生了变化。
最近,我遇到了同样的需求,并使用concat解决了这个问题,即通过将列连接到伪列中,然后在伪列上使用countDistinct
。
但我不能使用criteriaBuilder.concat
,因为它使用||
生成JPQL进行连接,而Hibernate在这方面遇到了麻烦。
幸运的是,有@Formula
,因此,我将伪列映射到具有@Formula
:的字段
@Entity
public class MyEntity {
@Column(name="col_a")
private String colA;
@Column(name="col_b")
private String colB;
@Formula("concat(col_a, col_b)") // <= THE TRICK
private String concated;
}
通过这种方式,我最终可以将concated
字段用于CriteriaBuilder.countDistinct
:
//...
Expression<?> exp = criteriaBuilder.countDistinct(entity.get("concated"));
criteriaQuery.select(exp);
TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);
return query.getSingleResult();
我希望JPA能够(或者希望已经)支持具有多个列的countDistinct
,那么所有这些混乱都可以避免。
似乎没有办法用JPA2 来做到这一点
您可以使用hibernate方言执行此任务。要做到这一点,请创建您自己的方言,它扩展了DB使用的方言(所有方言的列表),然后注册新函数。例如,我使用MySQL 5和InnoDB引擎:
public final class MyDialect extends MySQL5InnoDBDialect {
public MyDialect() {
super();
registerFunction("pairCountDistinct", new SQLFunctionTemplate(LongType.INSTANCE, "count(distinct ?1, ?2)"));
}
}
然后在persistence.xml中添加新属性:
<property name="hibernate.dialect" value="com.example.dialect.MyDialect" />
现在你可以使用这个功能:
// some init actions
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Long> criteria = builder.createQuery(Long.class);
final Root<SomeEntity> root = criteria.from(SomeEntity.class);
criteria.select(builder.function("pairCountDistinct", Long.class, root.get(SomeEntity_.field1), root.get(SomeEntity_.field2)));
final long result = entityManager.createQuery(criteria).getSingleResult();
// some close actions
正如其他人所指出的,使用concat方法构建一个由所有所需列组成的表达式,然后您可以将该表达式传递给countDistinct方法。
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<MyEntity> root = cb.from(MyEntity.class);
// build up an expression with multiple columns
Expression exp = root.get("column1");
exp = cb.concat(exp, root.get("column2");
exp = cb.concat(exp, root.get("column3");
cq.select(cb.contDistinct(exp));
var result = em.createQuery(cq).getSingleResult();
var count = Math.toIntExact(result);