Mit den 'Fall zu sein...wenn...dann...sonst...Ende' Konstrukt im "having" - Klausel in der JPA criteria-query
Den folgenden Kriterien Abfrage berechnet den Durchschnitt der Bewertung von verschiedenen Gruppen von Produkten.
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();
Generiert es die folgende SQL-Abfrage :
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
Den case...when
Struktur ersetzt null
Werte mit 0
, wenn der angegebene Ausdruck in der case
- Klausel ausgewertet, um null
.
Brauche ich die gleiche case...when
Konstrukt in der having
- Klausel, so dass die Gruppe von Zeilen zurück, die durch die group by
- Klausel gefiltert werden können, durch den Austausch null
mit 0
in der Liste der berechneten Werte durch die case...when
konstruieren, wenn überhaupt.
Entsprechend, die having
Klausel erzeugt werden sollen, wie
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
Könnte es möglich sein, wenn in der greaterThanOrEqualTo()
Methode selectExpression
statt roundExpression
ist gegeben, es ist aber nicht möglich. Dabei erzeugt eine compile-time Fehlermeldung "type mismatch" zwischen Expression<Integer>
und Expression<Object>
.
Also, wie kann ich die gleichen case...when
Struktur in der having
- Klausel wie in der select
- Klausel?
ich habe auch versucht, durch das entfernen der generische Typ-parameter Object
des Ausdrucks wie Expression selectExpression
aber, so zu tun, verursacht die NullPointerException
geworfen werden.
Außerdem, alias-Namen (prodId
, rating
) wie im select
Klausel haben keine Auswirkung in der generierten SQL wie gesehen werden kann. Warum Spalten sind nicht alias hier? Bin ich etwas fehlt?
Wenn Spalten alias dann sollte es möglich sein zu schreiben, die having
- Klausel wie folgt.
having rating>=0
sowie having
in den Kriterien der Abfrage sollte wie folgt sein,
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(join.<Integer>get("rating"), 0));
aber als Spalten nicht alias im select
Klausel, wirft er eine exception.
java.lang.IllegalArgumentException: Unable to resolve attribute [rating] against path [null]
Was ist der Weg zu bekommen um mit dieser situation? Jedenfalls die Zeilen zurückgegeben, die von Group by
gefiltert werden soll durch den Austausch null
mit 0
in der Liste der erzeugten Werte durch case...when
im select
- Klausel.
Ich bin mit JPA 2.0 zur Verfügung gestellt von Hibernate 4.2.7 final.
EDIT:
Habe ich versucht mit dem folgenden Ausdruck :
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
.when(quotExpression.isNull(), 0)
.<Integer>otherwise(roundExpression);
aber es verursacht die folgende Ausnahme ausgelöst :
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)
Wie kann der folgende Ausdruck arbeiten dann,
Expression<Integer> roundExpression = criteriaBuilder
.function("round", Integer.class, quotExpression);
haben beide den gleichen Typ?
Gibt es einen Weg, um die case...when
Struktur in der having
- Klausel?
BEARBEITEN
Ändern der Typ des Ausdrucks zu
Expression<Integer> selectExpression = criteriaBuilder
.<Integer>selectCase()
.when(quotExpression.isNull(), 0)
.<Integer>otherwise(roundExpression);
in EclipseLink (2.3.2) funktioniert folglich, es sein kann zur Verfügung gestellt in der having
- Klausel.
Im Falle von Hibernate Anbieter, wirft Sie das NullPoiterExcpetion
, wenn ein Versuch gemacht wird, um den Ausdruck zu ändern Art der selectCase()
(die gibt Expression<Object>
standardmäßig).
Update :
Dieses Problem weiterhin besteht, im Ruhezustand 5.0.5 Finale.
InformationsquelleAutor Tiny | 2013-11-20
Du musst angemeldet sein, um einen Kommentar abzugeben.
Dies ist sehr unwahrscheinlich, dass ein bug in Hibernate. Es gab einen technischen Fehler bei der Herstellung die Kriterien der Abfrage gegeben. Nehmen das gleiche Beispiel, aber in einer einfacheren form.
Lassen Sie uns davon ausgehen, dass wir daran interessiert sind, erzeugen die folgenden SQL-Abfrage.
Basierend auf der folgenden Tabelle in MySQL.
Dieser Tabelle
rating
hat offensichtlich viele-zu-eins-Beziehung mit einer anderen Tabelleproduct
(prod_id
ist der Fremdschlüssel verweisen auf Primärschlüsselprod_id
improduct
Tabelle).In dieser Frage sind wir nur daran interessiert, die
CASE
Konstrukt in derHAVING
- Klausel.Den folgenden Kriterien Abfrage,
Generiert die folgende korrekte SQL-Abfrage wie erwartet.
Für die technische Perspektive, betrachten Sie die folgende Zeile in die oben genannten Kriterien Abfrage.
Ihre analogen line-in der Frage geschrieben wurde, wie die folgenden.
Sehen, die ursprüngliche Ausdruck in der Frage um genau das gleiche :
Dieser Ausdruck wurde versucht zu übergeben
criteriaBuilder.greaterThanOrEqualTo()
wie folgt.Besondere Aufmerksamkeit widmen, um den zweiten parameter zu
greaterThanOrEqualTo()
oben. Es ist0
. Es solltecriteriaBuilder.literal(0)
statt daher die Ausnahme, wie erwähnt, in Frage stellen.Also, immer darauf bestehen, auf die Verwendung
CriteriaBuilder#literal(T value)
für Literale Werte, wenn notwendig, wie oben, während die Verwendung von Ausdrücken in derCriteriaBuilder#selectCase()
konstruieren.Getestet auf Hibernate 4.3.6 abschließende, Hibernate 5.0.5 Letzte alternativ.
ich werde versuchen zum ausführen der gleichen Abfrage auf EclipseLink (2.6.1 final) später auf. Sollte es nicht eine Macke mehr.EclipseLink hat überhaupt kein problem mit der modifizierten version der Abfrage, außer dass es erfordert eine
Object
geben Sie parameter an den Konstruktor-argument (formale parameter), wenn Konstruktor-Ausdrücke verwendet werden, anstelle vonTuple
dem diese Frage hat nichts damit zu tun, nachdem alle. Dies ist eine langjährige Bugs in EclipseLink noch behoben werden - ein analoges Beispiel.InformationsquelleAutor Tiny