`SELECT COUNT (*) FROM (SELECT DISTINCT ...)` в Hibernate или JPA - PullRequest
0 голосов
/ 18 мая 2018

Это похоже на тривиальный вариант использования Hibernate или JPA, но я несколько дней пытался заставить его работать.

У меня есть position класс сущностей, который имеет latitude, longitude и updateTime поля (среди прочих).Я хотел бы посчитать количество различных комбинаций этих трех полей, игнорируя остальные.В SQL это тривиально:

SELECT COUNT(*) FROM (SELECT DISTINCT LONGITUDE, LATITUDE, UPDATE_TIME FROM POSITION) AS TEMP;

Важно, чтобы я абстрагировал реализацию моей базы данных от остальной части моего приложения, потому что разные пользователи могут захотеть использовать разные механизмы баз данных.(Черт, я использую h2 для тестирования и mariadb для локального производства ...)

Я пытался перевести этот SQL-код в Java-код, используя синтаксис Hibernate или JPA, но я не могу понять, как.

РЕДАКТИРОВАТЬ - Здесь так близко, как я смог получить с помощью JPA (ref: https://en.wikibooks.org/wiki/Java_Persistence/Criteria)

public long getCountDistinctInFlightPositions() {
    Session session = sessionFactory.openSession();

    CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();

    CriteriaQuery<Tuple> innerQuery = criteriaBuilder.createTupleQuery();
    Root<Position> position = innerQuery.from(Position.class);
    innerQuery.multiselect(
        position.get("longitude"),
        position.get("latitude"),
        position.get("updateTime")
    );

    // The method countDistinct(Expression<?>) in the type CriteriaBuilder is not applicable for the arguments (CriteriaQuery<Tuple>)

    criteriaBuilder.countDistinct(innerQuery);

    return 1;
}

1 Ответ

0 голосов
/ 18 мая 2018

Вы можете сделать это следующим образом:

CriteriaQuery<Long> countQuery = cb.createQuery( Long.class );
Root<Position> root = countQuery.from( Position.class );

countQuery.select( cb.count( root.get( "id" ) ) );

Subquery<Integer> subQuery = countQuery.subquery( Integer.class );
Root<Position> subRoot = subQuery.from( Position.class );
subQuery.select( cb.min( subRoot.get( "id" ) ) );
subQuery.groupBy( subRoot.get( "longitude" ), 
  subRoot.get( "latitude" ), 
  subRoot.get( "updateTime" ) );

countQuery.where( root.get( "id" ).in( subQuery ) );

Long count = entityManager.createQuery( countQuery ).getSingleResult();

Это эффективно генерирует следующий SQL:

SELECT COUNT( p0.id ) FROM Position p0
 WHERE p0.id IN (
   SELECT MIN( p1.id )
     FROM Position p1
    GROUP BY p1.longitude, p1.latitude, p1.updateTime )

В сценарии, где у меня есть 3 строки и 2 из них имеют одинаковыеКортеж долготы, широты и времени обновления, запрос будет возвращать результат 2.

Убедитесь, что вы поддерживаете хороший индекс [Longitude, Latitude, UpdateTime] здесь, чтобы вы могли воспользоваться преимуществами более быстрой GROUPBY исполнение.PK уже индексирован по b-дереву, поэтому другие операции по отношению к COUNT / MIN уже должны быть легко учтены этим индексом.

...