SemaTrain Logo Ein Fachportal von SemaTrain

Subqueries, Views & Indexe

Jetzt kommt der „Werkzeugkasten“ für echte Praxisabfragen: Subqueries (Unterabfragen), Views (Sichten) und Indexe (Performance-Grundlagen).

Hinweis: Index-Details (Planner/EXPLAIN) sind DB-abhängig – wir bleiben bei robusten Grundlagen.

SQL Grundlagen Schulung – Kursbezug

Dieses Kapitel ist Teil des Lernpfads zur SQL Grundlagen Schulung. Termine & Buchung laufen über SemaTrain.de.

Toolkit Subqueries → Views → Indexe (Performance-Basics).

Worum geht’s?

Schema (Kontext)

Wir bleiben konsistent zum Lernpfad-Schema aus Kapitel 01–05:

Schema-Reminder (SQL)
-- kurse(kurs_id, kurs_name)
-- trainer(trainer_id, name)
-- termine(termin_id, kurs_id, trainer_id, start_datum, ort, format, dauer_tage, preis_eur)
-- buchungen(buchung_id, termin_id, name, email, created_at)

Lehr-/Lernziele

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)

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

  1. Schreibe eine Subquery mit EXISTS, die alle Kurse liefert, die mindestens einen Präsenz-Termin haben.
  2. Erstelle eine View v_praesenztermine (nur Präsenztermine, inkl. Kursname).
  3. 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

Quiz: Subqueries, Views & Indexe

1. (LZ1) Wofür eignet sich EXISTS besonders?

2. (LZ2) Was ist eine korrelierte Subquery?

3. (LZ3) Was ist eine View am ehesten?

4. (LZ4) Welche Aussage zu Indexen stimmt?

Praxisaufgabe

Mini-Projekt: Reporting-View „Termin-Report“ + Index-Setup (Toolkit)

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)

Stufe B – View (reporttaugliche „virtuelle Tabelle“)

Stufe C – Indexe (sinnvoll & minimal)

Musterlösung (Subqueries + View + Indexe)
Projektlösung: v_termin_report + EXISTS + Indexe (SQL)
-- ==========================================================
-- 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.