Как работает DISTINCT при использовании JPA и Hibernate - PullRequest
45 голосов
/ 28 августа 2009

С каким столбцом работает DISTINCT в JPA и можно ли его изменить?

Вот пример запроса JPA с использованием DISTINCT:

select DISTINCT c from Customer c

Что не имеет большого смысла - на каком столбце основано отличное? Указано ли это на сущности как аннотация, потому что я не смог ее найти?

Я хотел бы указать столбец, на котором нужно провести различие, что-то вроде:

select DISTINCT(c.name) c from Customer c

Я использую MySQL и Hibernate.

Ответы [ 6 ]

55 голосов
/ 24 октября 2012

Вы рядом.

select DISTINCT(c.name) from Customer c
13 голосов
/ 29 августа 2012
@Entity
@NamedQuery(name = "Customer.listUniqueNames", 
            query = "SELECT DISTINCT c.name FROM Customer c")
public class Customer {
        ...

        private String name;

        public static List<String> listUniqueNames() {
             return = getEntityManager().createNamedQuery(
                   "Customer.listUniqueNames", String.class)
                   .getResultList();
        }
}
10 голосов
/ 19 октября 2016

Я согласен с ответом kazanaki , и он мне помог. Я хотел выбрать всю сущность, поэтому я использовал

 select DISTINCT(c) from Customer c

В моем случае у меня есть отношение многие ко многим, и я хочу загружать сущности с коллекциями в одном запросе.

Я использовал LEFT JOIN FETCH, и в конце мне пришлось сделать результат отчетливым.

10 голосов
/ 28 августа 2009

Обновление: смотрите ответ с наибольшим количеством голосов, пожалуйста.

Мой собственный в настоящее время устарел. Хранится здесь только по историческим причинам.


Отличительный в HQL обычно требуется в Joins, а не в простых примерах, подобных вашему.

См. Также Как создать отдельный запрос в HQL

7 голосов
/ 21 ноября 2018

Как я объяснил в этой статье , в зависимости от базового типа запросов JPQL или Criteria API, DISTINCT имеет два значения в JPA.

Скалярные запросы

Для скалярных запросов, которые возвращают скалярную проекцию, например, следующий запрос:

List<Integer> publicationYears = entityManager
.createQuery(
    "select distinct year(p.createdOn) " +
    "from Post p " +
    "order by year(p.createdOn)", Integer.class)
.getResultList();

LOGGER.info("Publication years: {}", publicationYears);

Ключевое слово DISTINCT должно быть передано в базовый оператор SQL, поскольку мы хотим, чтобы механизм БД фильтровал дубликаты до возврата набора результатов:

SELECT DISTINCT
    extract(YEAR FROM p.created_on) AS col_0_0_
FROM
    post p
ORDER BY
    extract(YEAR FROM p.created_on)

-- Publication years: [2016, 2018]

Запросы сущностей

Для запросов сущностей DISTINCT имеет другое значение.

Без использования DISTINCT запрос, подобный следующему:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.title = :title", Post.class)
.setParameter(
    "title", 
    "High-Performance Java Persistence eBook has been released!"
)
.getResultList();

LOGGER.info(
    "Fetched the following Post entity identifiers: {}", 
    posts.stream().map(Post::getId).collect(Collectors.toList())
);

собирается присоединиться к post и post_comment таблицам следующим образом:

SELECT p.id AS id1_0_0_,
       pc.id AS id1_1_1_,
       p.created_on AS created_2_0_0_,
       p.title AS title3_0_0_,
       pc.post_id AS post_id3_1_1_,
       pc.review AS review2_1_1_,
       pc.post_id AS post_id3_1_0__
FROM   post p
LEFT OUTER JOIN
       post_comment pc ON p.id=pc.post_id
WHERE
       p.title='High-Performance Java Persistence eBook has been released!'

-- Fetched the following Post entity identifiers: [1, 1]

Но родительские записи post дублируются в наборе результатов для каждой связанной строки post_comment. По этой причине List из Post сущностей будет содержать дубликаты Post сущностей.

Чтобы исключить Post ссылки на сущности, нам нужно использовать DISTINCT:

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.title = :title", Post.class)
.setParameter(
    "title", 
    "High-Performance Java Persistence eBook has been released!"
)
.getResultList();

LOGGER.info(
    "Fetched the following Post entity identifiers: {}", 
    posts.stream().map(Post::getId).collect(Collectors.toList())
);

Но затем DISTINCT также передается в SQL-запрос, и это совсем не желательно:

SELECT DISTINCT
       p.id AS id1_0_0_,
       pc.id AS id1_1_1_,
       p.created_on AS created_2_0_0_,
       p.title AS title3_0_0_,
       pc.post_id AS post_id3_1_1_,
       pc.review AS review2_1_1_,
       pc.post_id AS post_id3_1_0__
FROM   post p
LEFT OUTER JOIN
       post_comment pc ON p.id=pc.post_id
WHERE
       p.title='High-Performance Java Persistence eBook has been released!'

-- Fetched the following Post entity identifiers: [1]

Передав DISTINCT в запрос SQL, ПЛАН ИСПОЛНЕНИЯ собирается выполнить дополнительную фазу Сортировка , которая добавляет издержки, не принося никакого значения, поскольку комбинации родитель-потомок всегда возвращают уникальные записи из-за столбец дочернего ПК:

Unique  (cost=23.71..23.72 rows=1 width=1068) (actual time=0.131..0.132 rows=2 loops=1)
  ->  Sort  (cost=23.71..23.71 rows=1 width=1068) (actual time=0.131..0.131 rows=2 loops=1)
        Sort Key: p.id, pc.id, p.created_on, pc.post_id, pc.review
        Sort Method: quicksort  Memory: 25kB
        ->  Hash Right Join  (cost=11.76..23.70 rows=1 width=1068) (actual time=0.054..0.058 rows=2 loops=1)
              Hash Cond: (pc.post_id = p.id)
              ->  Seq Scan on post_comment pc  (cost=0.00..11.40 rows=140 width=532) (actual time=0.010..0.010 rows=2 loops=1)
              ->  Hash  (cost=11.75..11.75 rows=1 width=528) (actual time=0.027..0.027 rows=1 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 9kB
                    ->  Seq Scan on post p  (cost=0.00..11.75 rows=1 width=528) (actual time=0.017..0.018 rows=1 loops=1)
                          Filter: ((title)::text = 'High-Performance Java Persistence eBook has been released!'::text)
                          Rows Removed by Filter: 3
Planning time: 0.227 ms
Execution time: 0.179 ms

Запросы сущностей с HINT_PASS_DISTINCT_THROUGH

Чтобы исключить фазу сортировки из плана выполнения, нам нужно использовать подсказку запроса HINT_PASS_DISTINCT_THROUGH JPA:

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.title = :title", Post.class)
.setParameter(
    "title", 
    "High-Performance Java Persistence eBook has been released!"
)
.setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
.getResultList();

LOGGER.info(
    "Fetched the following Post entity identifiers: {}", 
    posts.stream().map(Post::getId).collect(Collectors.toList())
);

И теперь SQL-запрос не будет содержать DISTINCT, но Post дубликаты ссылок на сущности будут удалены:

SELECT
       p.id AS id1_0_0_,
       pc.id AS id1_1_1_,
       p.created_on AS created_2_0_0_,
       p.title AS title3_0_0_,
       pc.post_id AS post_id3_1_1_,
       pc.review AS review2_1_1_,
       pc.post_id AS post_id3_1_0__
FROM   post p
LEFT OUTER JOIN
       post_comment pc ON p.id=pc.post_id
WHERE
       p.title='High-Performance Java Persistence eBook has been released!'

-- Fetched the following Post entity identifiers: [1]

И план выполнения подтвердит, что на этот раз у нас больше нет фазы дополнительной сортировки:

Hash Right Join  (cost=11.76..23.70 rows=1 width=1068) (actual time=0.066..0.069 rows=2 loops=1)
  Hash Cond: (pc.post_id = p.id)
  ->  Seq Scan on post_comment pc  (cost=0.00..11.40 rows=140 width=532) (actual time=0.011..0.011 rows=2 loops=1)
  ->  Hash  (cost=11.75..11.75 rows=1 width=528) (actual time=0.041..0.041 rows=1 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 9kB
        ->  Seq Scan on post p  (cost=0.00..11.75 rows=1 width=528) (actual time=0.036..0.037 rows=1 loops=1)
              Filter: ((title)::text = 'High-Performance Java Persistence eBook has been released!'::text)
              Rows Removed by Filter: 3
Planning time: 1.184 ms
Execution time: 0.160 ms
2 голосов
/ 29 июня 2017

Я бы использовал функцию выражения конструктора JPA. Смотрите также следующий ответ:

Выражение конструктора JPQL - org.hibernate.hql.ast.QuerySyntaxException: таблица не сопоставлена ​​

Следуя примеру в вопросе, это будет что-то вроде этого.

SELECT DISTINCT new com.mypackage.MyNameType(c.name) from Customer c
...