À 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
Vous devez vous connecter pour publier un commentaire.
C'est très rare d'être d'un bug en mode veille prolongée. Il y avait une erreur technique dans la fabrication des critères de requête donnée. En prenant le même exemple, mais dans une forme plus simple.
Supposons que nous nous intéressons à la génération de la requête SQL suivante.
Basé sur la table dans MySQL.
Ce tableau
rating
a évidemment plusieurs-à-une relation avec une autre tableproduct
(prod_id
est la clé étrangère référençant la clé primaireprod_id
dans leproduct
tableau).Dans cette question, nous nous intéressons uniquement à la
CASE
construire dans leHAVING
clause.Les critères suivants requête,
Génère la suite de corriger les requêtes SQL comme prévu.
Pour le point de vue technique, regardez la ligne suivante dans les critères ci-dessus requête.
Son analogue de la ligne en question a été rédigé comme suit.
Voir l'expression d'origine dans la question de faire exactement la même chose :
Cette expression a été tenté d'être passé à
criteriaBuilder.greaterThanOrEqualTo()
comme suit.Accorder une attention particulière à la deuxième paramètre à
greaterThanOrEqualTo()
ci-dessus. Il est0
. Il doit avoir étécriteriaBuilder.literal(0)
plutôt que par conséquent, l'exception, comme mentionné dans la question.Ainsi, toujours insister sur l'aide de
CriteriaBuilder#literal(T value)
pour des valeurs littérales chaque fois que nécessaire, comme l'a fait ci-dessus en utilisant des expressions dans laCriteriaBuilder#selectCase()
construire.Testé sur Hibernate 4.3.6 final, Hibernate 5.0.5 final sinon.
je vais essayer d'exécuter la même requête sur EclipseLink (2.6.1 finale) plus tard. Il ne devrait pas être une bizarrerie de plus.EclipseLink a pas de problème avec la version modifiée de la requête, sauf qu'il implique un
Object
paramètre de type de l'argument du constructeur (paramètre formel), si le constructeur expressions sont utilisées à la place deTuple
qui cette question n'a rien à faire avec, après tout. C'est depuis longtemps un bug dans EclipseLink encore être fixe - un exemple analogue.OriginalL'auteur Tiny