Wie funktioniert DISTINCT bei Verwendung von JPA und Hibernate?

Lesezeit: 9 Minuten

Benutzer-Avatar
Steve Claridge

Mit welcher Spalte arbeitet DISTINCT in JPA und ist es möglich, sie zu ändern?

Hier ist eine Beispiel-JPA-Abfrage mit DISTINCT:

select DISTINCT c from Customer c

Was nicht viel Sinn macht – auf welcher Spalte basiert die Unterscheidung? Ist es auf der Entität als Anmerkung angegeben, weil ich keine finden konnte?

Ich möchte die Spalte angeben, in der die Unterscheidung getroffen werden soll, etwa so:

select DISTINCT(c.name) c from Customer c

Ich verwende MySQL und Hibernate.

  • Welche spezifische Rolle spielt @Id im Lebenszyklus einer Entität?

    – Samuel Owino

    12. Januar 2020 um 0:26 Uhr

Du bist nah dran.

select DISTINCT(c.name) from Customer c

  • Dies gibt jedoch nur ein Array dieser Spalte zurück. Wie kann man mit diesem Ansatz ganze Entitäten zurückgeben?

    – Jh

    20. Dezember 2016 um 7:52 Uhr

  • @cen – was Sie verlangen, ist nicht logisch. Wenn ich zwei Kunden habe (id=1234, name=”Joe Customer”) und (id=2345, name=”Joe Customer”), welche sollten bei einer solchen Abfrage zurückgegeben werden? Die Ergebnisse wären undefiniert. Jetzt könnten Sie es mit so etwas erzwingen (nicht sicher, wie die Syntax dafür funktionieren würde, aber dies sollte die allgemeine Idee vermitteln): select c from Customer c where id in (select min(d.id) from Customer d group by d.name) … aber das ist situationsabhängig, denn Sie müssen einen Weg finden, basierend auf den verfügbaren Attributen, um eine der Entitäten auszuwählen.

    – Jules

    14. Februar 2018 um 21:00 Uhr

  • @Jules – in solchen Fällen ist es Ihnen normalerweise egal, welche zurückgegeben wird, sodass jede Auswahltechnik in Ordnung ist. Ich denke, mysql behandelt dieses Szenario sogar standardmäßig. Ich erinnere mich nicht an den genauen Anwendungsfall, den ich vor 2 Jahren hatte.

    – Jh

    15. Februar 2018 um 11:39 Uhr

  • @Jules gibt es eine Möglichkeit, das zurückgegebene Objektarray der Entität zuzuordnen.

    – Grünschnabel

    17. Mai 2018 um 5:48 Uhr

Benutzer-Avatar
Vlad Mihalcea

Abhängig vom zugrunde liegenden JPQL- oder Kriterien-API-Abfragetyp, DISTINCT hat in JPA zwei Bedeutungen.

Skalare Abfragen

Für skalare Abfragen, die eine Skalarprojektion zurückgeben, wie die folgende Abfrage:

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

Das DISTINCT Das Schlüsselwort sollte an die zugrunde liegende SQL-Anweisung übergeben werden, da wir möchten, dass die DB-Engine Duplikate filtert, bevor sie die Ergebnismenge zurückgibt:

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]

Entitätsabfragen

Für Entitätsabfragen DISTINCT hat eine andere Bedeutung.

Ohne zu benutzen DISTINCTeine Abfrage wie die folgende:

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

wird beitreten post und die post_comment Tabellen wie diese:

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]

Aber die Eltern post Datensätze werden in der Ergebnismenge für jeden zugeordneten Datensatz dupliziert post_comment die Zeile. Aus diesem Grund ist die List von Post Entitäten enthalten Duplikate Post Entitätsreferenzen.

Zur Beseitigung der Post Entitätsreferenzen, die wir verwenden müssen 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())
);

Aber dann DISTINCT wird auch an die SQL-Abfrage übergeben, und das ist überhaupt nicht erwünscht:

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]

Durch Vorbeigehen DISTINCT Zur SQL-Abfrage wird der AUSFÜHRUNGSPLAN eine Extraausführung ausführen Sortieren Phase, die Overhead hinzufügt, ohne einen Wert zu bringen, da die Eltern-Kind-Kombinationen aufgrund der untergeordneten PK-Spalte immer eindeutige Datensätze zurückgeben:

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

Entitätsabfragen mit HINT_PASS_DISTINCT_THROUGH

Um die Sort-Phase aus dem Ausführungsplan zu eliminieren, müssen wir die verwenden HINT_PASS_DISTINCT_THROUGH JPA-Abfragehinweis:

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

Und jetzt wird die SQL-Abfrage nicht enthalten DISTINCT aber Post Duplikate von Entitätsreferenzen werden entfernt:

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]

Und der Ausführungsplan wird bestätigen, dass wir diesmal keine zusätzliche Sortierphase mehr haben:

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

  • Letzte Woche gekauft, aber nicht ganz durch 😉 Wahrscheinlich das beste IT-Buch, das ich gelesen habe

    – Jaqen H’ghar

    18. Dezember 2018 um 13:09 Uhr

  • Danke, sehr hilfreiche Antwort!! Nachdem Sie den hier erwähnten Artikel und die Spring Data JPA-Referenzdokumente gelesen haben, haben Sie dies in meinem Spring Data JPA-Repository erreicht, indem Sie diese Anmerkung über der Methode hinzugefügt haben: @QueryHints(@QueryHint(name = "hibernate.query.passDistinctThrough", value = "false"))

    – dk7

    20. Dezember 2019 um 2:18 Uhr


  • @dk7 Das ist genau das, wonach ich gesucht habe. Vielen Dank!

    – Charvi

    13. März 2020 um 12:51 Uhr

  • Aber die Planungszeit hat zugenommen, warum ist das so?

    – muasif80

    16. April 2020 um 21:40 Uhr

  • @İsmailYavuz Die PASS_DISTINCT_THROUGH wurde umgesetzt von HHH-10965 und ist seit Hibernate ORM 5.2.2 verfügbar. Spring Boot 1.5.9 ist sehr alt und verwendet Hibernate ORM 5.0.12. Sie müssen also Ihre Abhängigkeiten aktualisieren, wenn Sie von diesen großartigen Funktionen profitieren möchten.

    – Vlad Mihalcea

    24. Juni 2020 um 18:10 Uhr

Benutzer-Avatar
Kasanaki

Update: Bitte sehen Sie sich die am besten bewertete Antwort an.

Meine eigene ist derzeit veraltet. Nur aus historischen Gründen hier aufbewahrt.


Distinct in HQL wird normalerweise in Joins benötigt und nicht in einfachen Beispielen wie Ihrem eigenen.

Siehe auch So erstellen Sie eine Distinct-Abfrage in HQL

  • Nichts für ungut, aber wie konnte das jemals als Antwort akzeptiert werden?

    – Pieter de Bié

    16. Juli 2015 um 11:00 Uhr

  • Es war die einzig gültige Antwort von 2009 bis 2012

    – Kasanaki

    12. September 2018 um 16:19 Uhr

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

Ich bin einverstanden mit Kasanaki‘s Antwort, und es hat mir geholfen. Ich wollte die gesamte Entität auswählen, also habe ich verwendet

 select DISTINCT(c) from Customer c

In meinem Fall habe ich eine Viele-zu-Viele-Beziehung und möchte Entitäten mit Sammlungen in einer Abfrage laden.

Ich habe LEFT JOIN FETCH verwendet und am Ende musste ich das Ergebnis eindeutig machen.

Benutzer-Avatar
Finstange

Ich würde die Konstruktorausdrucksfunktion von JPA verwenden. Siehe auch folgende Antwort:

JPQL-Konstruktorausdruck – org.hibernate.hql.ast.QuerySyntaxException: Tabelle ist nicht zugeordnet

Nach dem Beispiel in der Frage wäre es so etwas.

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

Benutzer-Avatar
ToddEmon

Ich füge eine Antwort hinzu, die etwas spezifisch ist, falls jemand auf das gleiche Problem wie ich stößt und diese Frage findet.

Ich habe JPQL mit Abfrageanmerkungen verwendet (keine Abfrageerstellung). Und ich musste eindeutige Werte für eine Entität erhalten, die es war eingebettet in eine andere Entität wurde die Beziehung über eine Viele-zu-Eins-Anmerkung bestätigt.

Ich habe zwei Datenbanktabellen:

  • MainEntitydie ich mit unterschiedlichen Werten möchte
  • LinkEntität, die eine Beziehungstabelle zwischen MainEntity und einer anderen Tabelle ist. Es hat einen zusammengesetzten Primärschlüssel, der aus seinen drei Spalten gebildet wird.

Im Java-Spring-Code führt dies zu drei implementierten Klassen:

LinkEntität:

@Entity
@Immutable
@Table(name="link_entity")
public class LinkEntity implements Entity {

    @EmbeddedId
    private LinkEntityPK pk;

    // ... Getter, setter, toString()
}

LinkEntityPK :

@Embeddable
public class LinkEntityPK implements Entity, Serializable {

    /** The main entity we want to have distinct values of */
    @ManyToOne
    @JoinColumn(name = "code_entity")
    private MainEntity mainEntity;

    /** */
    @Column(name = "code_pk2")
    private String codeOperation;

    /** */
    @Column(name = "code_pk3")
    private String codeFonction;

MainEntity :

@Entity
@Immutable
@Table(name = "main_entity")
public class MainEntity implements Entity {

    /** We use this for LinkEntity*/
    @Id
    @Column(name="code_entity")
    private String codeEntity;


    private String name;
    // And other attributes, getters and setters

Die letzte Abfrage, um eindeutige Werte für die Hauptentität zu erhalten, lautet also:

@Repository
public interface EntityRepository extends JpaRepository<LinkEntity, String> {

    @Query(
        "Select " +
            "Distinct linkEntity.pk.intervenant " +
        "From " +
            "LinkEntity as linkEntity " +
            "Join MainEntity as mainEntity On " +
                 "mainEntity = linkEntity.pk.mainEntity ")
    List<MainEntity> getMainEntityList();

}

Hoffe das kann jemandem helfen.

1137300cookie-checkWie funktioniert DISTINCT bei Verwendung von JPA und Hibernate?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy