如何使用JPA Criteria API / Hibernate按Case语句进行分组

时间:2018-02-06 00:30:48

标签: java hibernate jpa groovy criteria-api

我正在尝试执行类似以下的查询,通过case语句进行选择并按相同的case语句进行分组。

Select USER, 
  (CASE
    WHEN value between 0 AND 2 then '0-2'
    WHEN value between 3 AND 4 then '3-4'
    ELSE '5+'
  END) as CASE_STATEMENT ,
SUM(value)
.....
Group by user, CASE_STATEMENT

使用JPA 2.0 Criteria API和Hibernate。

我的测试用例看起来像......

    CriteriaBuilder cb = em.getCriteriaBuilder()
    CriteriaQuery cq = cb.createQuery(Tuple)
    def root = cq.from(TestEntity)
    def userGet = root.get('user')
    def valueGet = root.get('value')
    def caseExpr =
            cb.selectCase()
                .when(cb.between(valueGet, 0, 2), '0-2')
                .when(cb.between(valueGet, 3, 4), '3-4')
                .otherwise('5+')
    def sumExpr = cb.sum(valueGet)

    cq.multiselect([userGet, caseExpr, sumExpr])
    cq.groupBy([userGet, caseExpr])
    log(typedQuery.unwrap(Query).queryString)
    List<Tuple> tuples = typedQuery.getResultList()

queryString的日志语句读取

SELECT generatedAlias0.USER, 
   CASE 
     WHEN generatedAlias0.value BETWEEN 0 AND 2 THEN Cast(:param0 AS STRING) 
     WHEN generatedAlias0.value BETWEEN 3 AND 4 THEN Cast(:param1 AS STRING) 
     ELSE Cast(:param2 AS STRING) 
   END, 
   Sum(generatedAlias0.value) 
FROM   test AS generatedAlias0 
GROUP  BY generatedAlias0.USER, 
      CASE 
        WHEN generatedAlias0.value BETWEEN 0 AND 2 THEN Cast( 
        :param3 AS STRING) 
        WHEN generatedAlias0.value BETWEEN 3 AND 4 THEN Cast( 
        :param4 AS STRING) 
        ELSE Cast(:param5 AS STRING) 
      END 

调用typedQuery.getResultList()时,我收到以下错误声明

javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not extract ResultSet

Caused by: org.h2.jdbc.JdbcSQLException: Column "TESTENTITY0_.VALUE" must be in the GROUP BY list; SQL statement:

select testentity0_.user as col_0_0_, case when testentity0_.value between 0 and 2 then cast(? as varchar(255)) when testentity0_.value between 3 and 4 then cast(? as varchar(255)) else cast(? as varchar(255)) end as col_1_0_, sum(testentity0_.value) as col_2_0_ from test testentity0_ group by testentity0_.user , case when testentity0_.value between 0 and 2 then cast(? as varchar(255)) when testentity0_.value between 3 and 4 then cast(? as varchar(255)) else cast(? as varchar(255)) end [90016-194]

我尝试按表达式分组的方式有问题吗?我还尝试按别名和数字文字(1,2)

进行分组

我可以采用另一种方法来构建SQL以获得相同的结果吗?

感谢。

1 个答案:

答案 0 :(得分:1)

正如异常消息所暗示的那样,问题与DBMS级别的Group By语句有关。参见:https://www.percona.com/blog/2019/05/13/solve-query-failures-regarding-only_full_group_by-sql-mode/

要解决该错误,您必须

修改

除上述陈述外,下面讨论之后的发现是:

  • h2驱动程序org.h2.expression.ExpressionColumn类中引发了异常,它正在验证查询语法
  • 该解决方案要求在查询中设置和引用别名(在 case语句 subquery ),这在 Criteria API中目前无法实现(请参阅column aliases usually can't be referenced in the query itself
  • 一种解决方法是像这样创建NativeQuery:
List<Tuple> tuples = em.createNativeQuery(
"SELECT generatedAlias0.USER, " +
"   CASE " +
"     WHEN generatedAlias0.value BETWEEN 0 AND 2 THEN Cast(:param0 AS VARCHAR) " +
"     WHEN generatedAlias0.value BETWEEN 3 AND 4 THEN Cast(:param1 AS VARCHAR) " +
"     ELSE Cast(:param2 AS VARCHAR) " +
"   END c, " +
"   Sum(generatedAlias0.value) as sumvalue " +
"FROM test AS generatedAlias0 " +
"GROUP  BY generatedAlias0.USER, c "
)
.setParameter("param0", "0-2")
.setParameter("param1", "3-4")
.setParameter("param2", "5+")
.getResultList();