web-dev-qa-db-ja.com

JPAおよびHibernateを使用する場合のDISTINCTの仕組み

JTINではDISTINCTがどの列で機能しますか、それを変更することは可能ですか?

DISTINCTを使用したJPAクエリの例を次に示します。

select DISTINCT c from Customer c

これはあまり意味がありません-どの列が基づいているのですか?エンティティで見つからなかったため、アノテーションとして指定されていますか?

次のように、区別する列を指定したいと思います。

select DISTINCT(c.name) c from Customer c

MySQLとHibernateを使用しています。

46
Steve Claridge

更新:トップ投票の回答をご覧ください。

私自身は現在廃止されています。歴史的な理由でのみここに保存されています。


HQLの明確な区別は、通常、Joinで必要であり、独自の単純な例では必要ありません。

HQLでDistinctクエリを作成する方法 も参照してください。

10
kazanaki

あなたは近いです。

select DISTINCT(c.name) from Customer c
56
Αλέκος
@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();
        }
}
13
Tomasz

kazanakiの答えに同意し、助けてくれました。エンティティ全体を選択したかったので、

 select DISTINCT(c) from Customer c

私の場合、多対多の関係があり、1つのクエリでコレクションを含むエンティティを読み込みたいと思います。

LEFT JOIN FETCHを使用し、最後に結果を明確にする必要がありました。

10
Yan Khonski

この記事 で説明したように、JPQLまたはCriteria APIの基礎となるクエリタイプに応じて、DISTINCTはJPAで2つの意味を持ちます。

スカラークエリ

次のクエリのように、スカラープロジェクションを返すスカラークエリの場合:

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ステートメントに渡す必要があります。これは、結果セットを返す前にDBエンジンに重複をフィルター処理させるためです。

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())
);

次のようにpostpost_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クエリに渡すことにより、EXECUTION PLANは追加のSortフェーズを実行し、値をもたらすことなくオーバーヘッドを追加します。親子の組み合わせは、子PK列のために常に一意のレコードを返すためです。

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
7
Vlad Mihalcea

JPAのコンストラクター式機能を使用します。次の回答も参照してください。

JPQLコンストラクタ式-org.hibernate.hql.ast.QuerySyntaxException:テーブルはマップされていません

質問の例に従えば、このようなものになります。

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