The next criteria request calculates the average rating of various product groups.
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 0 if the specified expression in the case expression evaluates to null .
I need to have the same case...when construct in the having so that the group of rows returned by the group by clause can be filtered out by replacing null with 0 in the list of values ββcomputed by case...when to build, if any.
Accordingly, a having should be generated as
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 is possible if the greaterThanOrEqualTo() method specifies selectExpression instead of roundExpression , but this is not possible. This generates a compile-time error indicating a type mismatch between Expression<Integer> and Expression<Object> .
So, how can I have the same case...when structure in the having as in the select clause?
I also tried to remove a parameter of the type Object type of an expression of type Expression selectExpression , but a NullPointerException is NullPointerException .
In addition, the alias names ( prodId , rating ), as indicated in the select clause, have no effect in the generated SQL, as can be seen. Why are the columns here not aliases? Did I miss something?
If the columns have an alias, then it should be possible to write a having , as shown below.
having rating>=0
and having in the query criteria should be as follows:
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(join.<Integer>get("rating"), 0));
but since the columns are not flattened in the select clause, it throws an exception.
java.lang.IllegalArgumentException: Unable to resolve attribute [rating] against path [null]
What is the way around this? In any case, the rows returned by group by should be filtered out, replacing null with 0 in the list of values ββcreated by case...when in the select clause.
I am using JPA 2.0 provided by the final version of Hibernate 4.2.7.
EDIT:
I tried with the following expression:
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase() .when(quotExpression.isNull(), 0) .<Integer>otherwise(roundExpression);
but this caused the following exception:
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?
Expression<Integer> roundExpression = criteriaBuilder .function("round", Integer.class, quotExpression);
both have the same type?
Is there a way to place a case...when structure in a having ?
EDIT
Change expression type to
Expression<Integer> selectExpression = criteriaBuilder .<Integer>selectCase() .when(quotExpression.isNull(), 0) .<Integer>otherwise(roundExpression);
works in EclipseLink (2.3.2) , so it can be accessed in the having .
In the case of the Hibernate provider, it throws NullPoiterExcpetion if an attempt is made to change the type of the selectCase() expression (which returns the default Expression<Object> ).
Update:
This issue still persists in the final of Hibernate 5.0.5.