À l'aide de 'l'affaire...quand...then...else...end" construire dans le "avoir des" clause dans JPA critères de requête

Les critères suivants requête calcule la moyenne de la notation de différents groupes de produits.

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();

Il génère la requête SQL suivante :

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 

La case...when structure remplace null valeurs avec 0, si l'expression spécifiée dans le case clause est évalué à null.

J'ai besoin de la même case...when construire dans le having clause de sorte que le groupe de lignes retournées par la group by clause peut être filtré par le remplacement de null avec 0 dans la liste des valeurs calculées par le case...when construire, le cas échéant.

En conséquence, la having clause doit être généré comme

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

Il pourrait être possible, si dans le greaterThanOrEqualTo() méthode, selectExpression au lieu de roundExpression est donné, mais il n'est pas possible. Ce faisant, génère une erreur de compilation, en indiquant le type d'incompatibilité entre Expression<Integer> et Expression<Object>.

Alors, comment puis-je avoir la même case...when de la structure dans le having clause dans le select clause?

j'ai aussi essayé en supprimant le paramètre de type générique Object de l'expression comme Expression selectExpression mais ce faisant, a causé la NullPointerException à être jetés.


En outre, les noms d'alias (prodId, rating) comme indiqué dans le select clause ont aucun effet dans le SQL généré comme on peut le voir. Pourquoi les colonnes ne sont pas lissés ici? Ai-je raté quelque chose?

Si les colonnes sont des alias ensuite, il devrait être possible d'écrire le having clause comme suit.

having rating>=0

et having dans les critères de la requête doit être comme suit,

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(join.<Integer>get("rating"), 0));

mais que les colonnes ne sont pas des alias dans la select clause, il lève une exception.

java.lang.IllegalArgumentException: Unable to resolve attribute [rating] against path [null]

Quel est le moyen de contourner cette situation? De toute façon, le nombre de lignes retournées par Group by doit être filtrée par le remplacement de null avec 0 dans la liste des valeurs produites par case...when dans le select clause.


Je suis en utilisant JPA 2.0 fourni par Hibernate 4.2.7 final.


EDIT:

J'ai essayé avec l'expression suivante :

Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);

mais il a causé la suivante 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)

Comment l'expression suivante à travailler,

Expression<Integer> roundExpression = criteriaBuilder
                              .function("round", Integer.class, quotExpression);

les deux ont le même type?

Est-il un moyen de mettre la case...when de la structure dans le having clause?


MODIFIER

L'évolution de l'expression type de

Expression<Integer> selectExpression = criteriaBuilder
                                       .<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);

dans EclipseLink (2.3.2) œuvres par conséquent, il peut être mis à disposition dans le having clause.

En cas de Hibernate fournisseur, il jette la NullPoiterExcpetion, si une tentative est faite pour changer le type d'expression de selectCase() (qui renvoie Expression<Object> par défaut).


Mise à jour :

Ce problème persiste encore dans Hibernate 5.0.5 final.

OriginalL'auteur Tiny | 2013-11-20