HQL zagnieżdżony SELECT w ORDER BY

0

Cześć potrzebuję pomocy.
Mam problem z zapytaniem HQL i nie mogę odnaleźć przyczyny błędu który jest zwracany.

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: query
 at org.hibernate.hql.internal.ast.QuerySyntaxException.convert(QuerySyntaxException.java:74) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.hql.internal.ast.ErrorTracker.throwQueryException(ErrorTracker.java:93) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:278) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:155) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:604) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:716) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:779) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.query.criteria.internal.CriteriaQueryImpl$1.buildCompiledQuery(CriteriaQueryImpl.java:310) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.query.criteria.internal.compile.CriteriaCompiler.compile(CriteriaCompiler.java:165) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:742) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:113) ~[hibernate-core-5.4.15.Final.jar:5.4.15.Final]
 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
 at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
 at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]

@Entity
@Table(name = "places")
@SequenceGenerator(name = "place_seq", sequenceName = "places_id_seq", initialValue = 1, allocationSize = 1)
@Setter
@Getter
public class Place implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(generator = "place_seq", strategy = GenerationType.SEQUENCE)
    private Long id;

    @OneToOne
    @JoinColumn(name = "point_id")
    private Point point;

    @OneToMany
    @JoinColumn(name = "place_id")
    private List<PlaceHasProduct> placeHasProducts = new LinkedList<>();
	...
}

@Entity
@Table(name = "place_has_products")
@SequenceGenerator(name = "place_has_product_seq", sequenceName = "place_has_products_id_seq", initialValue = 1, allocationSize = 1)
@Setter
@Getter
public class PlaceHasProduct implements Serializable {
    private static final long serialVersionUID = 2L;

    @Id
    @GeneratedValue(generator = "place_has_product_seq", strategy = GenerationType.SEQUENCE)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
	...
}

@Entity
@Table(name = "products")
@SequenceGenerator(name = "product_seq", sequenceName = "products_id_seq", initialValue = 1, allocationSize = 1)
@Setter
@Getter
public class Product implements Serializable {
    private static final long serialVersionUID = 3L;

    @Id
    @GeneratedValue(generator = "product_seq", strategy = GenerationType.SEQUENCE)
    private Long id;

    private String name;
	...
}

Zapytanie HQL

select
 distinct generatedAlias0
from
 app.place.model.Place as generatedAlias0
inner join generatedAlias0.placeHasProducts as generatedAlias1 
inner join generatedAlias1.productHasOutsourcings as generatedAlias2 
where
 ( generatedAlias0.activeSubscription = true ) and 
 ( generatedAlias0.point in (10L, 189L) ) and 
 ( generatedAlias1.product in (1L, 2L, 3L) )
order by
 (
  select
   distinct count(generatedAlias3) 
  from
   app.place.model.PlaceHasProduct as generatedAlias3
  where
   ( generatedAlias3=generatedAlias0.placeHasProducts ) 
  and 
   ( generatedAlias3.product in (1L, 2L, 3L) )
 ) asc

Zapytanie jest generowane przez CriteriaBuilder i wygląda prawidłowo. Znalazłem kilka postów z podobnym problemem (~2008r.), H nie mógł poprawnie sparsowac selecta w orderze. Ponoć zostało to poprawione w wersji Hibernate 3.6 więc sądzę że przyczyna leży zupełnie w innym miejscu.

3

Zapytanie jest generowane przez CriteriaBuilder i wygląda prawidłowo.

Nie wiem co tu wygląda prawidłowo.
Pomijając, że do tego gówienka (HQL) nie mogłem znaleźć żadnej pełnej dokumentacji składni to z tego fragmentu :
https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.2/html/developing_hibernate_applications/hibernate_query_language#:~:text=in%20the%20database.-,4.3.%C2%A0About%20HQL%20Ordering,the%20select%20clause%20for%20any%20of%20the%20previous%20expression%20types,-HQL%20does%20not

wynika, że nie ma czegoś takiego jak subquery w order by.
Zresztą nie bardzo kumam co to miałoby być.

2

Criteria API nie generuje HQL tylko SQL. Pokaż ten kod z Criteria.

To zapytanie w ogóle przechodzi Ci w SQL studio, czy czego tam uzywasz?

0

@jarekr000000: zgadzam się, HQL nie jest najlepiej udokumentowany.
Ogólnie sortowanie 'Place' ma się odbyć po ilości 'Product', która jest przypisane do encji 'PlaceHasProduct', gdzie lista tych produktów jest zmienna w zależności od tego co zaznaczył user.
@Charles_Ray: Z tego co się orientuje to Criteria robi HQL a HQL robi już czysty SQL do DB.

Kod dodam jutro.
Teraz mnie naszło, że alias generatedAlias3 jest tą samą tabelą co generatedAlias1, pomijając że wkleiłem nie to co trzeba :( (poprawiłem w poście z pamięci jaki był ostatecznie chyba dobrze).
Zmęczenie robi swoje.

0
private TypedQuery build(Pageable pageable, SearchPlaceForm searchPlaceForm, List<Point> pointList){
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Place> placeCriteriaQuery = criteriaBuilder.createQuery(Place.class);
        Root<Place> place = placeCriteriaQuery.from(Place.class);
        List<Order> orders = new LinkedList<>();
        List<Predicate> predicateList = new LinkedList<>();


        // Kryteria
        predicateList.add(criteriaBuilder.isTrue(place.get("activeSubscription")));

        // Odległości punktów
        if (!conditionPoint(predicateList, criteriaBuilder, place, pointList)) {
            return new PageImpl<>(new LinkedList<Place>(), pageable, 0);
        }
        
        Join j = conditionProductAndOutsourcing(predicateList, criteriaBuilder, place, searchPlaceForm.getProducts(), searchPlaceForm.getServices());
        if (j != null) {

            Subquery sub = placeCriteriaQuery.subquery(Long.class);
            Root<PlaceHasProduct> php = sub.from(PlaceHasProduct.class);
            sub.select(criteriaBuilder.count(php.get("id")));
            sub.distinct(true);

            CriteriaBuilder.In<Long> inClause = criteriaBuilder.in(php.get("product"));
            searchPlaceForm.getProducts().stream().forEach(e -> {
                inClause.value(e);
            });
            sub.where(criteriaBuilder.and(criteriaBuilder.equal(php, place.get("placeHasProducts")), inClause));
            orders.add(criteriaBuilder.asc(sub));
        }

        //Sortowanie
        orders.addAll(QueryUtils.toOrders(pageable.getSort(), place, criteriaBuilder));

        // Budowanie zapytania
        placeCriteriaQuery.select(place);
        placeCriteriaQuery.distinct(true);
        Predicate predicate = buildPredicate(predicateList, criteriaBuilder);
        if (predicate != null) {
            placeCriteriaQuery.where(predicate);
        }
        placeCriteriaQuery.orderBy(orders);

        return entityManager.createQuery(placeCriteriaQuery);
}

private Join<Place, PlaceHasProduct> conditionProductAndOutsourcing(List<Predicate> inList, CriteriaBuilder criteriaBuilder, From root, List<Long> productIdList) {
        if (productIdList.size() > 0) {
            Join<Place, PlaceHasProduct> join = root.join("placeHasProducts", JoinType.INNER);
            CriteriaBuilder.In<Long> inClause = criteriaBuilder.in(join.get("product"));
            productIdList.stream().forEach(e -> {
                inClause.value(e);
            });
            inList.add(inClause);
            return join;
        }
        return null;
    }
select
 distinct generatedAlias0
from
 app.place.model.Place as generatedAlias0
inner join generatedAlias0.placeHasProducts as generatedAlias1
where
 ( generatedAlias0.activeSubscription = true ) and 
 ( generatedAlias0.point in (10L, 11L, 12L, 13L, 14L, 182L, 183L, 184L, 185L, 186L, 187L, 188L, 189L) ) and
 ( generatedAlias1.product in (7L, 8L) )
order by
 (select distinct count(generatedAlias2.id) from app.place.model.PlaceHasProduct as generatedAlias2 where ( generatedAlias2=generatedAlias0.placeHasProducts ) and ( generatedAlias2.product in (7L, 8L) ))
asc
2

Co to w ogóle znaczy:

order by
(select distinct count (...)

Jeśli dla jednego wiersza wyjdzie [1,3,5] (kolejne wiersze), a dla drugiego [2,3] to który jest pierwszy, a który drugi?

2

To raczej do działu „programistyczne WTF” :)

HQL to taki deklaratywny język zapytań, którego się nie używa bezpośrednio, ale tworzy się drzewo wyrażeń w imperatywnym kodzie Javy - tzw CriteriaQuery - aby wygenerować ten HQL dynamicznie. Wszystko na koniec i tak jest transpilowane do SQL, ale nie wszystkie funkcjonalności SQL działają.

Gdzie my jako społeczność Javy popełniliśmy błąd?

2

No wlasnie, jak chcesz sortowac po count, to jakis group by by sie przydal ;)

2
ArchitektSpaghetti napisał(a):

Gdzie my jako społeczność Javy popełniliśmy błąd?

Formerly called J2EE, the first version of Java EE platform was officially released in December 1999 with 10 specifications. Among these specifications, there were Servlets and JavaServer Pages (JSP) for data presentation, Enterprise JavaBeans

Tak to się zaczęło. (BTW. byłem entuzjasto tego :-) )

0

Dziękuję wszystkim za udział w temacie. Pomogliście wyłapać kilka błędów. To co chciałem osiągnąć w CB jest niemożliwe zbyt czasochłonne.
Skończyło się na tym, że stworzyłem widok, do którego jest budowane native query na zasadzie

SELECT to_co_trzeba FROM tabela JOIN _dużo_tego_ WHERE _dużo_tego_ ORDER BY _subselect_do_sortowania_ DESC;

Zdaję sobie sprawę, że nie jest to optymalne rozwiązanie, ale klientowi dopowiada czas działania finalnej odpowiedzi i jest świadomy długu technologicznego (w stosunku do kosztów).

1

klientowi dopowiada czas działania finalnej odpowiedzi

To zaden argument, bo w zaleznosci jak szybko beda rosnac dane i jak query jest zoptymalizowane - moze to kleknac lub nie :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1