The following criteria query calculates the average of rating of different groups of products.
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple>criteriaQuery=criteriaBuilder.createQuery(Tuple.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<Product>entityType=metamodel.entity(Product.class);
Root<Product>root=criteriaQuery.from(entityType);
SetJoin<Product, Rating> join = root.join(Product_.ratingSet, JoinType.LEFT);
Expression<Number> quotExpression = criteriaBuilder.quot(criteriaBuilder.sum(join.get(Rating_.ratingNum)), criteriaBuilder.count(join.get(Rating_.ratingNum)));
Expression<Integer> roundExpression = criteriaBuilder.function("round", Integer.class, quotExpression);
Expression<Object> selectExpression = criteriaBuilder.selectCase().when(quotExpression.isNull(), 0).otherwise(roundExpression );
criteriaQuery.select(criteriaBuilder.tuple(root.get(Product_.prodId).alias("prodId"), selectExpression.alias("rating")));
criteriaQuery.groupBy(root.get(Product_.prodId));
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(roundExpression, 0));
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(Product_.prodId)));
TypedQuery<Tuple> typedQuery = entityManager.createQuery(criteriaQuery);
List<Tuple> tuples = typedQuery.getResultList();
It generates the following SQL query :
SELECT product0_.prod_id AS col_0_0_,
CASE
WHEN Sum(ratingset1_.rating_num) / Count(ratingset1_.rating_num) IS
NULL THEN
0
ELSE Round(Sum(ratingset1_.rating_num) / Count(ratingset1_.rating_num))
END AS col_1_0_
FROM social_networking.product product0_
LEFT OUTER JOIN social_networking.rating ratingset1_
ON product0_.prod_id = ratingset1_.prod_id
GROUP BY product0_.prod_id
HAVING Round(Sum(ratingset1_.rating_num) / Count(ratingset1_.rating_num)) >= 0
ORDER BY product0_.prod_id DESC
The case...when
structure replaces null
values with 0
, if the specified expression in the case
clause is evaluated to null
.
I need the same case...when
construct in the having
clause so that the group of rows returned by the group by
clause can be filtered by replacing null
with 0
in the list of values calculated by the case...when
construct, if any.
Accordingly, the having
clause should be generated like
HAVING
(CASE
WHEN Sum(ratingset1_.rating_num)/Count(ratingset1_.rating_num) IS
NULL THEN 0
ELSE Round(sum(ratingset1_.rating_num)/Count(ratingset1_.rating_num))
END)>=0
It could be possible, if in the greaterThanOrEqualTo()
method, selectExpression
instead of roundExpression
is given but it is not possible. Doing so, generates a compile-time error indicating type mismatch between Expression<Integer>
and Expression<Object>
.
So how can I have the same case...when
structure in the having
clause as in the select
clause?
I have also tried by removing the generic type parameter Object
of the expression like Expression selectExpression
but doing so, caused the NullPointerException
to be thrown.
Moreover, alias names (prodId
, rating
) as given in the select
clause have no effect in the generated SQL as can be seen. Why columns are not aliased here? Am I missing something?
If columns are aliased then, it should be possible to write the having
clause just like follows.
having rating>=0
and having
in the criteria query should be as follows,
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(join.<Integer>get("rating"), 0));
but as columns are not aliased in the select
clause, it throws an exception.
java.lang.IllegalArgumentException: Unable to resolve attribute [rating] against path [null]
What is the way to get around this situation? Anyway, the rows returned by Group by
should be filtered by replacing null
with 0
in the list of values produced by case...when
in the select
clause.
I'm using JPA 2.0 provided by Hibernate 4.2.7 final.
EDIT:
I have tried with the following expression :
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
.when(quotExpression.isNull(), 0)
.<Integer>otherwise(roundExpression);
but it caused the following exception to be thrown :
Caused by: java.lang.NullPointerException
at java.lang.Class.isAssignableFrom(Native Method)
at org.hibernate.ejb.criteria.ValueHandlerFactory.isNumeric(ValueHandlerFactory.java:69)
at org.hibernate.ejb.criteria.predicate.ComparisonPredicate.<init>(ComparisonPredicate.java:69)
at org.hibernate.ejb.criteria.CriteriaBuilderImpl.greaterThanOrEqualTo(CriteriaBuilderImpl.java:468)
How can the following expression work then,
Expression<Integer> roundExpression = criteriaBuilder
.function("round", Integer.class, quotExpression);
both have the same type?
Is there a way to put the case...when
structure in the having
clause?
EDIT
Changing the expression type to
Expression<Integer> selectExpression = criteriaBuilder
.<Integer>selectCase()
.when(quotExpression.isNull(), 0)
.<Integer>otherwise(roundExpression);
in EclipseLink (2.3.2) works hence, it can be made available in the having
clause.
In case of Hibernate provider, it throws the NullPoiterExcpetion
, if an attempt is made to change the expression type of selectCase()
(which returns Expression<Object>
by default).
Update :
This issue still persists in Hibernate 5.0.5 final.