Skip to content

Spring Data fails to detect proper enumeration type for case/when/then in sum with CriteriaBuilder #3871

Closed as not planned
@stefan-schilling

Description

@stefan-schilling

Hello,
our application uses CriteriaBuilder to select data from the DB. In this case, we're returning conditional results:

SELECT SUM(CASE WHEN x THEN 1 ELSE 2)/100 FROM y;

The x in the code above represents an enumeration, which is saved using its ordinal numbers.
This works perfectly when using Spring Boot/Spring Data 3.2.12 for tests and productive usage.
But switching to version 3.4.5, it suddenly fails for the tests tests (H2 - 2.2.224 / 2.3.232) - while the productive (Postgres) usage continues to succeed.

Hibernate: 
    select
        oe1_0.common_name,
        ((sum(case oe1_0.our_status 
            when ? 
                then ? 
            else ? 
    end)/count(oe1_0.id))*cast(? as float(53))) 
from
    our_entity oe1_0 
group by
    1
2025-05-06T21:17:48.787+02:00  WARN 266149 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 90015, SQLState: 90015
2025-05-06T21:17:48.787+02:00 ERROR 266149 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SUM oder AVG auf falschem Datentyp für "SUM(CASE OE1_0.OUR_STATUS WHEN ?1 THEN ?2 ELSE ?3 END)"
SUM or AVG on wrong data type for "SUM(CASE OE1_0.OUR_STATUS WHEN ?1 THEN ?2 ELSE ?3 END)"; SQL statement:
select oe1_0.common_name,((sum(case oe1_0.our_status when ? then ? else ? end)/count(oe1_0.id))*cast(? as float(53))) from our_entity oe1_0 group by 1 [90015-232]

org.hibernate.exception.SQLGrammarException: could not prepare statement [SUM oder AVG auf falschem Datentyp für "SUM(CASE OE1_0.OUR_STATUS WHEN ?1 THEN ?2 ELSE ?3 END)"
SUM or AVG on wrong data type for "SUM(CASE OE1_0.OUR_STATUS WHEN ?1 THEN ?2 ELSE ?3 END)"; SQL statement:
select oe1_0.common_name,((sum(case oe1_0.our_status when ? then ? else ? end)/count(oe1_0.id))*cast(? as float(53))) from our_entity oe1_0 group by 1 [90015-232]] [select oe1_0.common_name,((sum(case oe1_0.our_status when ? then ? else ? end)/count(oe1_0.id))*cast(? as float(53))) from our_entity oe1_0 group by 1]

	at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:66)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:58)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:108)
	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:191)
	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:153)
	at org.hibernate.sql.exec.internal.StandardStatementCreator.createStatement(StandardStatementCreator.java:49)
	at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:235)
	at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:171)
	at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.<init>(JdbcValuesResultSetImpl.java:74)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165)
	at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152)
	at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442)
	at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362)
	at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380)
	at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143)
	at org.hibernate.query.Query.getResultList(Query.java:120)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:419)
	at jdk.proxy2/jdk.proxy2.$Proxy134.getResultList(Unknown Source)
	at org.spring.data.jpa.bugs.OurService.createPercentageQuery(OurService.java:41)
	at org.spring.data.jpa.bugs.OurServiceTest.test(OurServiceTest.java:64)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: SUM oder AVG auf falschem Datentyp für "SUM(CASE OE1_0.OUR_STATUS WHEN ?1 THEN ?2 ELSE ?3 END)"
SUM or AVG on wrong data type for "SUM(CASE OE1_0.OUR_STATUS WHEN ?1 THEN ?2 ELSE ?3 END)"; SQL statement:
select oe1_0.common_name,((sum(case oe1_0.our_status when ? then ? else ? end)/count(oe1_0.id))*cast(? as float(53))) from our_entity oe1_0 group by 1 [90015-232]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:644)
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
	at org.h2.message.DbException.get(DbException.java:223)
	at org.h2.message.DbException.get(DbException.java:199)
	at org.h2.expression.aggregate.Aggregate.optimize(Aggregate.java:1009)
	at org.h2.expression.BinaryOperation.optimize(BinaryOperation.java:131)
	at org.h2.expression.BinaryOperation.optimize(BinaryOperation.java:131)
	at org.h2.command.query.Select.optimizeExpressionsAndPreserveAliases(Select.java:1344)
	at org.h2.command.query.Select.prepareExpressions(Select.java:1225)
	at org.h2.command.query.Query.prepare(Query.java:232)
	at org.h2.command.Parser.prepareCommand(Parser.java:489)
	at org.h2.engine.SessionLocal.prepareLocal(SessionLocal.java:645)
	at org.h2.engine.SessionLocal.prepareCommand(SessionLocal.java:561)
	at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1164)
	at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:93)
	at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:687)
	at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:342)
	at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java)
	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$4.doPrepare(StatementPreparerImpl.java:151)
	at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:180)
	... 25 more

The query we're using - look esp. for the criteriaBuilder.selectCase(..).when(..).otherwise(...).as(..) part

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

        CriteriaQuery<OurEntityPercentageStatus> criteriaQuery = criteriaBuilder.createQuery(OurEntityPercentageStatus.class);
        Root<OurEntity> root = criteriaQuery.from(OurEntity.class);
        criteriaQuery.groupBy(root.get("commonName"));

        OurStatus ourStatus = OurStatus.STATUS_1;

        // Get percentage of ourEntities in status STATUS_1. PSEUDOCODE: SUM(case when our_status == STATUS_1 then 1 else 0 end) / cast(Count(*) as decimal)) * 100  as percentage_out
        Selection<Double> percentageOutSelection = criteriaBuilder.prod(criteriaBuilder.quot(criteriaBuilder.sum(
            criteriaBuilder.selectCase(root.get("ourStatus"))
                .when(ourStatus.ordinal(), 1.0)
                .otherwise(0.0)
                .as(Double.class)), criteriaBuilder.count(root)), 100.0).as(Double.class).alias("percentage_out");

        //Get objects with commonName and percentage_out
        criteriaQuery.select(
            criteriaBuilder.construct(OurEntityPercentageStatus.class, root.get("commonName"), percentageOutSelection));
        CriteriaQuery<OurEntityPercentageStatus> finalQuery = criteriaQuery.multiselect(root.get("commonName"),
            percentageOutSelection);
        return entityManager.createQuery(finalQuery).getResultList();

Somehow, the return type of the values produced by the CASE/WHEN/THEN is not detected properly, leading to org.h2.expression.SimpleCase:optimize(SessionLocal) returns TypeInfo.TYPE_VARCHAR - which is not correct. But I don't know, if that's a

  1. H2 issue (which would make sense, since the implementation works when using productive Postgres)
  2. Spring Data issue (which would make sense, since it's sufficient to switch the Spring Boot/Data version from 3.2.12 to 3.4.5, but keep the H2 version stable to bring up the issue)

I've attached
query-sum-enumeration-test.zip, containing the sample code - just jump in the pom.xml between the two Spring Boot versions.

Thanks for your help and do not hesitate to contact me, if you have any questions.
Stefan

Metadata

Metadata

Assignees

No one assigned

    Labels

    for: external-projectFor an external project and not something we can fix

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions