Avec quelle colonne DISTINCT fonctionne-t-il dans JPA et est-il possible de le changer?
Voici un exemple de requête JPA utilisant DISTINCT:
select DISTINCT c from Customer c
Ce qui n'a pas beaucoup de sens - sur quelle colonne le distinct est-il basé? Est-il spécifié sur l'entité comme une annotation car je n'en ai pas trouvé?
Je voudrais spécifier la colonne sur laquelle faire la distinction, quelque chose comme:
select DISTINCT(c.name) c from Customer c
J'utilise MySQL et Hibernate.
Mise à jour: Voir la réponse la plus votée s'il vous plaît.
Le mien est actuellement obsolète. Seulement conservé ici pour des raisons historiques.
Distinct dans HQL est généralement nécessaire dans les jointures et non dans des exemples simples comme le vôtre.
Voir aussi Comment créer une requête distincte dans HQL
Tu es proche.
select DISTINCT(c.name) from Customer c
@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();
}
}
Je suis d'accord avec la réponse de kazanaki, et cela m'a aidé. Je voulais sélectionner l'entité entière, j'ai donc utilisé
select DISTINCT(c) from Customer c
Dans mon cas, j'ai une relation plusieurs-à-plusieurs et je souhaite charger des entités avec des collections dans une seule requête.
J'ai utilisé LEFT JOIN FETCH et à la fin j'ai dû rendre le résultat distinct.
Comme je l'ai expliqué dans cet article , en fonction du type de requête sous-jacent JPQL ou Criteria API, DISTINCT
a deux significations dans JPA.
Pour les requêtes scalaires, qui renvoient une projection scalaire, comme la requête suivante:
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);
Le mot clé DISTINCT
doit être transmis à l'instruction SQL sous-jacente car nous voulons que le moteur de base de données filtre les doublons avant de renvoyer le jeu de résultats:
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]
Pour les requêtes d'entité, DISTINCT
a une signification différente.
Sans utiliser DISTINCT
, une requête comme la suivante:
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())
);
va JOINDRE les tables post
et post_comment
comme ceci:
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]
Mais les enregistrements parent post
sont dupliqués dans le jeu de résultats pour chaque ligne post_comment
Associée. Pour cette raison, les entités List
des entités Post
contiendront des références d'entité Post
en double.
Pour éliminer les références d'entité Post
, nous devons utiliser 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())
);
Mais alors DISTINCT
est également passé à la requête SQL, et ce n'est pas du tout souhaitable:
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]
En passant DISTINCT
à la requête SQL, le PLAN D'EXÉCUTION va exécuter une phase supplémentaire Sort qui ajoute une surcharge sans apporter de valeur car les combinaisons parent-enfant renvoient toujours des enregistrements uniques en raison de la colonne PK enfant:
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
Pour éliminer la phase de tri du plan d'exécution, nous devons utiliser l'indice de requête JPA HINT_PASS_DISTINCT_THROUGH
:
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())
);
Et maintenant, la requête SQL ne contiendra pas DISTINCT
mais Post
les doublons de référence d'entité vont être supprimés:
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]
Et le plan d'exécution va confirmer que nous n'avons plus de phase de tri supplémentaire cette fois:
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
J'utiliserais la fonction d'expression constructeur de JPA. Voir également la réponse suivante:
Suivant l'exemple de la question, ce serait quelque chose comme ça.
SELECT DISTINCT new com.mypackage.MyNameType(c.name) from Customer c