(LZ1) Subqueries mit IN und EXISTS korrekt einsetzen.
(LZ2) korrelierte Subqueries erkennen und typische Fehler vermeiden.
(LZ3) Views erstellen/verwenden (CREATE VIEW) und Einsatzzwecke erklären.
(LZ4) Index-Grundidee verstehen und einfache Indexe anlegen (CREATE INDEX).
Subqueries
Unterabfragen kommen häufig in „Filter-Fragen“ vor: Zeige X, wenn Bedingung in anderer Tabelle erfüllt ist.
Subquery mit IN (SQL)
-- LZ1: IN-Subquery
-- Alle Kurse, die mindestens einen Termin haben
SELECT k.kurs_id, k.kurs_name
FROM kurse AS k
WHERE k.kurs_id IN (
SELECT t.kurs_id
FROM termine AS t
);
Subquery mit EXISTS (SQL)
-- LZ1: EXISTS ist oft robuster als IN (insb. bei NULLs) und gut lesbar
SELECT k.kurs_id, k.kurs_name
FROM kurse AS k
WHERE EXISTS (
SELECT 1
FROM termine AS t
WHERE t.kurs_id = k.kurs_id
);
Korrelierte Subquery (und warum sie „korreliert“ heißt)
„Korrelierte“ Subqueries referenzieren Spalten der äußeren Query (hier: k.kurs_id).
Der Optimizer kann oft trotzdem sehr gut damit umgehen.
Korrelierte Subquery: COUNT je Kurs (SQL)
-- LZ2: Korrelierte Subquery: Termine pro Kurs (Alternative zu GROUP BY)
SELECT
k.kurs_id,
k.kurs_name,
(
SELECT COUNT(*)
FROM termine AS t
WHERE t.kurs_id = k.kurs_id
) AS anzahl_termine
FROM kurse AS k
ORDER BY anzahl_termine DESC, k.kurs_name ASC;
Praxis: Häufig ist LEFT JOIN + GROUP BY performanter/üblicher – korrelierte Subqueries sind aber super für „pro Zeile“ Werte.
Views
Views sind gespeicherte Abfragen: gut für Reports, stabile Schnittstellen und weniger Wiederholung.
CREATE VIEW: v_termin_report (SQL)
-- LZ3: View anlegen (Termine + Kurs + optional Trainer)
-- Hinweis: Viele Systeme erlauben kein ORDER BY in der View-Definition.
CREATE VIEW v_termin_report AS
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
t.dauer_tage,
t.preis_eur,
k.kurs_id,
k.kurs_name,
COALESCE(tr.name, '(noch offen)') AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id;
SELECT aus View (SQL)
-- View nutzen wie eine Tabelle
SELECT kurs_name, start_datum, format, ort, trainer
FROM v_termin_report
WHERE format = 'Praesenz'
ORDER BY start_datum ASC, ort ASC;
Views ändern/löschen
DROP VIEW (Basics) (SQL)
-- DB-abhängig:
-- PostgreSQL/MySQL: CREATE OR REPLACE VIEW ...
-- SQLite: ersetzen = DROP + CREATE
DROP VIEW v_termin_report;
Indexe (Performance-Grundlagen)
Index = „Nachschlage-Struktur“ (meist B-Tree), beschleunigt Suchen/Joinen/Sortieren.
Indexe kosten Speicher und verlangsamen INSERT/UPDATE/DELETE (weil Index mitgepflegt wird).
Gute Kandidaten: Spalten in WHERE, JOIN ON, ORDER BY – v. a. bei großen Tabellen.
CREATE INDEX: Join-Spalte (SQL)
-- LZ4: Index auf Foreign-Key/Join-Spalte (typisch sehr sinnvoll)
CREATE INDEX idx_termine_kurs_id ON termine(kurs_id);
CREATE INDEX: WHERE/ORDER BY Spalte (SQL)
-- LZ4: Index auf häufige Filter-/Sortierspalte
CREATE INDEX idx_termine_start_datum ON termine(start_datum);
Optional: Composite Index (Format + Datum) (SQL)
-- Optional: zusammengesetzter Index für typische Query:
-- WHERE format='Online' ORDER BY start_datum
CREATE INDEX idx_termine_format_start_datum ON termine(format, start_datum);
Wann ein Index NICHT hilft
Wenn fast alle Zeilen sowieso gelesen werden (sehr unselektive Filter).
Bei sehr kleinen Tabellen (Full Scan ist oft günstiger).
Wenn die Spalte oft geändert wird (hohe Update-Kosten).
Wenn Funktionen auf der Spalte genutzt werden (z. B. LOWER(name)) ohne passenden (funktionalen) Index – DB-abhängig.
Praxisaufgabe
Schreibe eine Subquery mit EXISTS, die alle Kurse liefert, die mindestens einen Präsenz-Termin haben.
Erstelle eine View v_praesenztermine (nur Präsenztermine, inkl. Kursname).
Lege einen Index an, der deine Filter-/Sortier-Query unterstützt (z. B. termine(format, start_datum)).
Lösungsvorschlag anzeigen
Lösung: EXISTS + VIEW + INDEX (SQL)
-- 1) EXISTS: Kurse mit Präsenz-Terminen
SELECT k.kurs_id, k.kurs_name
FROM kurse AS k
WHERE EXISTS (
SELECT 1
FROM termine AS t
WHERE t.kurs_id = k.kurs_id
AND t.format = 'Praesenz'
);
-- 2) View: nur Präsenz-Termine (Kursname + Termin-Infos)
CREATE VIEW v_praesenztermine AS
SELECT
k.kurs_name,
t.termin_id,
t.start_datum,
t.ort,
t.dauer_tage,
t.preis_eur
FROM kurse AS k
INNER JOIN termine AS t
ON t.kurs_id = k.kurs_id
WHERE t.format = 'Praesenz';
-- 3) Index: typische Filter+Sortierung
CREATE INDEX idx_termine_format_start_datum ON termine(format, start_datum);
Kurz-Takeaways
LZ1:EXISTS ist häufig die sichere Wahl (NULL-Probleme bei IN vermeiden).
LZ2: Korrelierte Subqueries referenzieren die äußere Query – gut für „pro Zeile“ Werte.
LZ3: Views reduzieren Wiederholung und stabilisieren Report-Abfragen.
LZ4: Indexe helfen bei großen Daten & selektiven Filtern – kosten aber Schreib-Performance.
Sie bauen eine reporttaugliche Sicht (View) auf Basis der bisherigen Kapitel:
Termine + Kursname + optional Trainer.
Dazu kommen 2 typische Subquery-Fälle (EXISTS) und ein sinnvolles Minimal-Index-Set für Filter/Sortierung.
Beitrag zu den Lehr-/Lernzielen:LZ1 (IN/EXISTS), LZ2 (korreliert), LZ3 (VIEW), LZ4 (INDEX).
Praxis-Stack
Subquery → View → Index
Stufe A – Subqueries (Filter-Fragen)
Kurse mit mindestens einem Termin (EXISTS).
Kurse mit mindestens einem Online-Termin (EXISTS + Filter).
Stufe B – View (reporttaugliche „virtuelle Tabelle“)
Erstelle v_termin_report: Termin + Kursname + Trainer (optional).
Nutze COALESCE, damit „Trainer fehlt“ sauber angezeigt wird.
Stufe C – Indexe (sinnvoll & minimal)
Index auf termine(kurs_id) für JOIN/Lookups.
Index auf termine(start_datum) für Range-Filter/Sortierung.
Optional: termine(format, start_datum) für „Format + Datum“.
-- ==========================================================
-- Stufe A – Subqueries (EXISTS)
-- ==========================================================
-- (A1) Alle Kurse, die mindestens einen Termin haben
SELECT k.kurs_id, k.kurs_name
FROM kurse AS k
WHERE EXISTS (
SELECT 1
FROM termine AS t
WHERE t.kurs_id = k.kurs_id
)
ORDER BY k.kurs_name ASC;
-- (A2) Alle Kurse, die mindestens einen ONLINE-Termin haben
SELECT k.kurs_id, k.kurs_name
FROM kurse AS k
WHERE EXISTS (
SELECT 1
FROM termine AS t
WHERE t.kurs_id = k.kurs_id
AND t.format = 'Online'
)
ORDER BY k.kurs_name ASC;
-- ==========================================================
-- Stufe B – View: Report „Termine + Kurs + Trainer (optional)“
-- SQLite: ersetzen = DROP VIEW + CREATE VIEW
-- ==========================================================
-- DROP VIEW v_termin_report;
CREATE VIEW v_termin_report AS
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
t.dauer_tage,
t.preis_eur,
k.kurs_id,
k.kurs_name,
COALESCE(tr.name, '(noch offen)') AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id;
-- View nutzen: Online-Terminsuche
SELECT termin_id, start_datum, ort, kurs_name, trainer, preis_eur
FROM v_termin_report
WHERE format = 'Online'
ORDER BY start_datum ASC, ort ASC;
-- ==========================================================
-- Stufe C – Indexe (Performance-Basics)
-- ==========================================================
CREATE INDEX idx_termine_kurs_id
ON termine(kurs_id);
CREATE INDEX idx_termine_start_datum
ON termine(start_datum);
-- Optional: häufiges Muster (Format + Datum)
CREATE INDEX idx_termine_format_start_datum
ON termine(format, start_datum);
-- Hinweis: zu viele Indexe verlangsamen INSERT/UPDATE/DELETE.
Ausblick: Im nächsten Kapitel (Transaktionen) kombinieren wir „Safe-Change“ (Kap. 05)
mit echten Mehrschritt-Änderungen – und sehen, warum DML ohne Transaktion riskant ist.