Skip to content

Kapitel 10: Unterabfragen (Subqueries)

Unterabfrage (Subquery) ist eine Abfrage, die innerhalb einer anderen Abfrage verwendet wird.

Hauptabfrage (äußere Abfrage)

  SELECT ... WHERE feld IN (Unterabfrage)

                          Innere Abfrage (wird zuerst ausgeführt)

10.1 Was ist eine Unterabfrage?

Einfaches Beispiel:

sql
-- Frage: Welche Benutzer haben eine Bestellung getätigt?

-- Ohne Unterabfrage (mit JOIN):
SELECT DISTINCT users.username
FROM users
INNER JOIN orders ON users.user_id = orders.user_id;

-- MIT Unterabfrage:
SELECT username 
FROM users
WHERE user_id IN (SELECT DISTINCT user_id FROM orders);

Vorteile von Unterabfragen:

  • ✅ Logisch getrennt (einfacher zu verstehen)
  • ✅ Keine DISTINCT nötig (bei manchen Fällen)
  • ✅ Flexibler bei komplexen Bedingungen

10.2 Verwendung in WHERE (Häufigste Verwendung) ⭐⭐

1. Mit IN / NOT IN

sql
-- Benutzer, die EINE Bestellung haben
SELECT username 
FROM users
WHERE user_id IN (SELECT user_id FROM orders);

-- Benutzer, die KEINE Bestellung haben
SELECT username 
FROM users
WHERE user_id NOT IN (SELECT user_id FROM orders);

Beispiel aus der Praxis:

sql
-- Produkte, die bereits bestellt wurden
SELECT produkt_name
FROM products
WHERE produkt_id IN (SELECT produkt_id FROM orders);

-- Produkte, die NIE bestellt wurden (Lagerüberbestand!)
SELECT produkt_name
FROM products
WHERE produkt_id NOT IN (SELECT produkt_id FROM orders);

2. Mit Vergleichsoperatoren (=, >, <, etc.)

sql
-- Benutzer mit dem höchsten Umsatz
SELECT username
FROM users
WHERE user_id = (
    SELECT user_id 
    FROM orders 
    GROUP BY user_id 
    ORDER BY SUM(preis * menge) DESC 
    LIMIT 1
);

Wichtig: Wenn du = verwendest, muss die Unterabfrage genau einen Wert zurückgeben!

3. Mit EXISTS / NOT EXISTS

EXISTS prüft, ob Datensätze in der Unterabfrage existieren.

sql
-- Benutzer, die mindestens EINE Bestellung haben
SELECT username
FROM users u
WHERE EXISTS (
    SELECT 1 
    FROM orders o 
    WHERE o.user_id = u.user_id
);

-- Benutzer, die KEINE Bestellung haben
SELECT username
FROM users u
WHERE NOT EXISTS (
    SELECT 1 
    FROM orders o 
    WHERE o.user_id = u.user_id
);

Vorteil von EXISTS: Sehr schnell bei großen Datenmengen!

10.3 Verwendung in FROM (Temporäre Tabelle)

Eine Unterabfrage im FROM-Teil wird wie eine temporäre Tabelle behandelt.

sql
-- Durchschnittsalter pro Stadt berechnen, dann Städte über dem Gesamtdurchschnitt finden
SELECT stadt, durchschnittsalter
FROM (
    SELECT stadt, AVG(alter) AS durchschnittsalter
    FROM users
    GROUP BY stadt
) AS stadt_durchschnitte
WHERE durchschnittsalter > (SELECT AVG(alter) FROM users);

Erklärung:

  1. Innere Abfrage: Berechnet Durchschnittsalter pro Stadt → temporäre Tabelle stadt_durchschnitte
  2. Äußere Abfrage: Filtert die temporäre Tabelle

Wichtig: Eine temporäre Tabelle muss einen Alias haben (AS stadt_durchschnitte)!

10.4 Vergleich: Unterabfragen vs. Tabellenverbindungen

Unterabfrage (Subquery)Tabellenverbindung (JOIN)
LesbarkeitBesser (logisch getrennt)Schlechter (komplexer)
GeschwindigkeitLangsamer (meistens)Schneller (meistens)
KomplexitätEinfacher bei einfachen FällenBesser bei komplexen Abfragen
EmpfehlungFür EinsteigerFür Fortgeschrittene

Beispiel: Gleichwertige Abfragen

Mit Unterabfrage:

sql
SELECT username
FROM users
WHERE user_id IN (SELECT user_id FROM orders);

Mit JOIN:

sql
SELECT DISTINCT users.username
FROM users
INNER JOIN orders ON users.user_id = orders.user_id;

Beide geben das gleiche Ergebnis!

10.5 Praxisbeispiel: Komplexe Bedingungsabfragen

Szenario: Eine Online-Shop-Datenbank analysieren.

Schritt 1: Testdaten vorbereiten

sql
USE shop_db;

-- Unterabfrage: Produkte finden, die teurer als der Durchschnitt sind
SELECT produkt_name, preis
FROM products
WHERE preis > (SELECT AVG(preis) FROM products)
ORDER BY preis DESC;

Schritt 2: Benutzer mit überdurchschnittlichem Umsatz

sql
SELECT 
    u.username,
    SUM(o.menge * o.preis) AS umsatz
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.username
HAVING umsatz > (
    SELECT AVG(gesamt_umsatz) 
    FROM (
        SELECT SUM(menge * preis) AS gesamt_umsatz
        FROM orders
        GROUP BY user_id
    ) AS umsatz_pro_user
);

Häufige Fehler für Einsteiger

Fehler 1: Unterabfrage gibt mehrere Werte zurück (bei =)

sql
-- ❌ Falsch: Unterabfrage gibt mehrere user_id zurück
SELECT username 
FROM users
WHERE user_id = (SELECT user_id FROM orders);  -- FEHLER!

-- ✅ Richtig: IN verwenden
SELECT username 
FROM users
WHERE user_id IN (SELECT user_id FROM orders);

Fehler 2: Temporäre Tabelle ohne Alias

sql
-- ❌ Falsch: Kein Alias
SELECT * 
FROM (SELECT stadt, COUNT(*) FROM users GROUP BY stadt);

-- ✅ Richtig: Mit Alias
SELECT * 
FROM (SELECT stadt, COUNT(*) AS anzahl FROM users GROUP BY stadt) AS temp;

Fehler 3: Unterabfrage in SELECT (Performance-Problem)

sql
-- ❌ Langsam (Unterabfrage wird für jede Zeile ausgeführt!)
SELECT 
    username,
    (SELECT COUNT(*) FROM orders WHERE user_id = users.user_id) AS anzahl_bestellungen
FROM users;

-- ✅ Schneller (mit JOIN)
SELECT 
    u.username,
    COUNT(o.bestell_id) AS anzahl_bestellungen
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.username;

Zusammenfassung

In diesem Kapitel hast du gelernt:

  • ✅ Was Unterabfragen (Subqueries) sind
  • ✅ Unterabfragen in WHERE zu verwenden (IN, NOT IN, EXISTS)
  • ✅ Unterabfragen in FROM zu verwenden (temporäre Tabellen)
  • ✅ Den Unterschied zwischen Unterabfragen und JOINs
  • ✅ Wann Unterabfragen sinnvoll sind (und wann nicht)

Übungen

  1. Theorie: Was ist der Unterschied zwischen IN und EXISTS?
  2. Praxis: Finde alle Produkte, die nicht bestellt wurden (NOT IN)
  3. Praxis: Finde die Benutzer mit dem höchsten Umsatz (Unterabfrage mit LIMIT 1)
  4. Praxis: Erstelle eine temporäre Tabelle, die das Durchschnittsalter pro Stadt berechnet

Nächstes Kapitel

Im nächsten Kapitel lernen wir Transaktionen und Indizes - wichtig für Datensicherheit und Performance!

Frei für alle Anfänger