JPA: Alle Entities wenn in anderer Tabelle nur ein Eintrag mit selbem Attribut


#1

Entschuldigt den Titel des Threads aber besser kann ich es kurz nicht erklären.

Ich habe zwei Entities (H, I) die keine Beziehung (im JPA Sinne) zueinander haben.
Beide haben jedoch ein Attribut (a) über dass diese in Beziehung gebracht werden können. Nun ist es jedoch so, dass es zu einem Wert H.a mehrere Einträge von I mit gleichem Wert a geben kann.
Ich Suche nun genau die H welche genau einem I mit H.a = I.a entsprechen.

Das nun folgende Beispiel verdeutlicht hoffentlich das Problem und funktioniert auch. Ich Frage mich jedoch ob es hier ein einfacheres Query gibt.


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class I {

    @Id
    @GeneratedValue
    private Long id;

    private String a;

    protected I() {
    }

    public I(final String a) {
        this.a = a;
    }

    public Long getId() {
        return id;
    }

    protected void setId(final Long id) {
        this.id = id;
    }

    public String getA() {
        return a;
    }

    protected void setA(final String a) {
        this.a = a;
    }

    @Override
    public String toString() {
        return "I[" + a + "]";
    }

}```
```package de.mvitz.jpa.foo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class H {

    @Id
    @GeneratedValue
    private Long id;

    private String a;

    protected H() {
    }

    public H(final String a) {
        this.a = a;
    }

    public Long getId() {
        return id;
    }

    protected void setId(final Long id) {
        this.id = id;
    }

    public String getA() {
        return a;
    }

    protected void setA(final String a) {
        this.a = a;
    }

    @Override
    public String toString() {
        return "H[" + a + "]";
    }

}```
```package de.mvitz.jpa.foo;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(final String[] args) {
        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("h2foo");
        try {
            // create initial data
            EntityManager em = emf.createEntityManager();
            em.getTransaction().begin();

            em.persist(new I("1"));
            em.persist(new I("1"));
            em.persist(new I("2"));

            em.persist(new H("1"));
            em.persist(new H("2"));
            em.persist(new H("3"));

            em.getTransaction().commit();
            em.close();

            // query
            em = emf.createEntityManager();
            em.getTransaction().begin();

            final List<H> result = em
                    .createQuery(
                            "SELECT h FROM H h WHERE"
                                    + " h.a IN (SELECT h.a FROM H h, I i WHERE h.a = i.a GROUP BY h.a HAVING COUNT(1) = 1)",
                            H.class).getResultList();
            System.out.println(result);

            em.getTransaction().commit();
            em.close();
        } finally {
            emf.close();
        }
    }

}```

Das korrekte Ergebnis hier ist, dass die Liste result genau ein H enthält mit a = 2.

#2

Subselects sind i.d.R. imperformant, werden nicht von allen Engines unterstützt und sind schlecht zu lesen. Was Du tun willst, heißt JOIN. Und zwar im ersten Schritt: Finde alle H, für die es mindestens ein I mit gleichem a gibt. Das ist der klassische INNER JOIN. Damit findest Du aber eben leider auch Hs, bei denen es mehrere Is mit gleichem a gibt. Es gilt nun im zweiten Schritt, diese Ergebnismenge in geeigenter Weise einzuschränken, sodass nur noch die mit genau einem gleichen I.a übrig bleiben. Da kommen GROUP BY und HAVING ins Spiel. So müsste es gehen:
[SQL]SELECT h.a FROM H h
INNER JOIN I i
ON h.a=i.a
GROUP BY h.a
HAVING COUNT(i.a)=1
[/SQL]
Habe leider gerade keine DB, um das zu testen. Habe es nur durch einen online Validator gejagt. Der hat es als valide angesehen.

P.S. Ach ja und nicht vergessen, einen Index auf die Spalten H.a und I.a zu legen. Das beschleunigt die Suche bei größeren Datenmengen.


#3

Kannst du ggf. den Link zum Onlinevalidator noch posten?


#4

Ja, ist tatsächlich etwas schwierig zu finden. Bei einer google-Suche bin ich über ein DB-Forum auf folgende Seite gestoßen: http://developer.mimer.se/validator/index.htm (JavaScript muss erlaubt sein).

Übrigens warst Du mit Deinem Versuch garnicht so falsch unterwegs. Der Statementteil
[SQL]SELECT h.a FROM H h, I i
WHERE h.a = i.a[/SQL]
ist äquivalent zu meinem INNER JOIN Statement
[SQL]SELECT h.a FROM H h
INNER JOIN I i
ON h.a=i.a[/SQL]
Ich finde die INNER JOIN Syntax nur besser, um klar zu machen, dass man hier zwei Tabellen über gemeinsamkeiten verbinden möchte. WHERE benutze ich nur für die Suche nach Suchparametern etc.
Der Teil hier war ja auch schon fast richtig:
[SQL]GROUB BY h.a HAVING COUNT(1) = 1[/SQL]
Und jetzt ist mir auch klar, warum Du glaubtest, darum noch einen SELECT bauen zu müssen. Damit JPA die Entities draus machen kann, gell? Das ist unnötig. Du musst im SELECT-Statement entweder ein * haben. Oder, wenn es nicht geht (so wie hier) ALLE Spalten mit aufnehmen. Also:
[SQL]SELECT h.a …alle anderen Spalten… FROM H h …Rest des INNER JOIN/GROUP BY/HAVING-Statements[/SQL]


#5

Danke für den Link.

Das doppelte Select war da noch drin, weil im Originalcode nicht nur H sondern auch I zurückgegeben wurde. Das habe ich dann zumindest mit nur dem einfachen Select nicht geschafft weil dann I nicht in der GROUP BY Clause auftaucht.

Bezüglich WHERE und JOIN gebe ich dir recht, der Code war auch im Original von einem Kollegen und ich wollte nur wissen ob es da noch einen einfacheren weg gibt. Aber so passt jetzt alles, danke.


#6

So ist es semantisch in SQL ja auch gedacht. Und wenn ich mich recht entsinne, hat das auch manchmal Einfluss auf den Ausführungsplan des SQL-Servers - zumindest bei MySQL. Bei anderen Servern wird es aber wohl ähnlich sein.


#7

Ca se passe plutot bien. Андрей Леницкий Официальный сайт / Official site of Andrey Lenitsky Lire aussi Epargne, retraite, hopital…