Schwere Probeklausur: Datenbanken - Praktische Umsetzung

SQL, PostgreSQL, Trigger, Transaktionen, JDBC/JPA, verteilte Datenbanken und NoSQL

Grundlage: Beispielklausuraufgaben aus temp_db-praktisch sowie die Vorlesungsskripte 1 bis 8 aus temp_db-praktisch. Die Aufgaben orientieren sich an den Beispieltypen, sind aber bewusst schwerer kombiniert.

Bearbeitungszeit120 Minuten
Umfang110 Punkte
DBMS-BezugPostgreSQL-Syntax, sofern nicht anders angegeben

Bearbeitungshinweise

Gemeinsames Schema für die SQL-Aufgaben

Die Aufgaben 1 bis 7 verwenden, falls nicht anders angegeben, das folgende Musikplattform-Schema. Attribute mit PK sind Primärschlüssel, Attribute mit FK sind Fremdschlüssel.

Tabelle Attribute Semantik und wichtige Regeln
artist artist_id PK, name, country Künstler. name ist fachlich eindeutig, aber im Ausgangsschema noch nicht technisch abgesichert.
genre genre_id PK, name Musikgenre. name soll eindeutig und nicht leer sein.
album album_id PK, artist_id FK, title, release_year Ein Album gehört genau einem Künstler. release_year darf unbekannt sein.
track track_id PK, album_id FK, genre_id FK, name, milliseconds, unit_price Ein Track gehört genau einem Album. Genre kann bei Altdaten fehlen. Preise sind nicht negativ.
customer customer_id PK, email, country Kundenkonto. email ist eindeutig, country kann fehlen.
invoice invoice_id PK, customer_id FK, invoice_date, billing_country, status, total status ist OPEN, PAID oder CANCELLED. total soll der Summe der Rechnungspositionen entsprechen.
invoice_line invoice_id FK, line_no, track_id FK, quantity, unit_price Primärschlüssel: (invoice_id, line_no). quantity ist positiv, unit_price ist nicht negativ.
playlist playlist_id PK, name Playlist. Namen sind fachlich eindeutig.
playlist_track playlist_id FK, track_id FK, position Primärschlüssel: (playlist_id, track_id). Innerhalb einer Playlist ist position eindeutig.

Klausuraufgaben

14 PunkteAufgabe 1: DDL und Integrität

Erstellen Sie die Tabelle artist_contract für Verträge zwischen Künstlern und Labels.

Attribut Anforderung
contract_idautomatisch erzeugter Primärschlüssel
artist_idPflichtfeld, Fremdschlüssel auf artist(artist_id); beim Löschen des Künstlers sollen Verträge mitgelöscht werden
label_namePflichtfeld, maximal 80 Zeichen
starts_onPflichtfeld
ends_onoptional, darf nicht vor starts_on liegen
royalty_rateZahl zwischen 0 und 0,5 einschließlich 0,5, aber größer als 0
exclusivePflichtfeld, Standardwert true
  1. Schreiben Sie den vollständigen CREATE TABLE-Befehl inklusive sinnvoll benannter Constraints. Ein Künstler darf beim selben Label nicht zweimal am selben Startdatum beginnen. 9 Punkte
  2. Schreiben Sie einen ALTER TABLE-Befehl, der für album sicherstellt, dass (artist_id, title) eindeutig ist. Nennen Sie ein praktisches Risiko beim Ausführen auf Bestandsdaten. 3 Punkte
  3. Entscheiden Sie für die folgenden Einfügungen, ob sie zulässig sind. Begründen Sie kurz. Gehen Sie davon aus, dass artist_id = 7 existiert und artist_id = 999 nicht existiert. 2 Punkte
INSERT INTO artist_contract
  (artist_id, label_name, starts_on, ends_on, royalty_rate)
VALUES
  (7, 'Blue Note', DATE '2024-01-01', NULL, 0.18);

INSERT INTO artist_contract
  (artist_id, label_name, starts_on, ends_on, royalty_rate)
VALUES
  (999, 'Ghost Label', DATE '2024-01-01', NULL, 0.18);

INSERT INTO artist_contract
  (artist_id, label_name, starts_on, ends_on, royalty_rate)
VALUES
  (7, 'Blue Note', DATE '2024-01-01', DATE '2023-12-31', 0.60);

10 PunkteAufgabe 2: Ergebnis einer SQL-Abfrage ermitteln

Gegeben sind die folgenden Tabelleninhalte. Ermitteln Sie das exakte Ergebnis der Abfrage inklusive Spaltennamen und Sortierung.

customer
customer_idemailcountry
1anna@example.orgDE
2bo@example.orgDE
3carla@example.orgFR
4dan@example.orgNULL
invoice
invoice_idcustomer_idstatustotalinvoice_date
101PAID20.002025-01-05
111OPEN15.002025-01-06
122PAIDNULL2025-02-01
133PAID35.002025-02-03
143CANCELLED40.002025-02-04
SELECT
  COALESCE(c.country, '?') AS land,
  COUNT(i.invoice_id) AS rechnungen,
  COUNT(*) AS join_zeilen,
  SUM(
    CASE
      WHEN i.status = 'PAID' THEN COALESCE(i.total, 0)
      ELSE 0
    END
  ) AS paid_total
FROM customer c
LEFT JOIN invoice i
  ON i.customer_id = c.customer_id
 AND i.status <> 'CANCELLED'
GROUP BY COALESCE(c.country, '?')
HAVING COUNT(i.invoice_id) >= 1
    OR COALESCE(c.country, '?') = '?'
ORDER BY paid_total DESC, land ASC;

Erklären Sie zusätzlich in einem Satz, warum die Position des Filters i.status <> 'CANCELLED' hier wichtig ist.

18 PunkteAufgabe 3: SQL-Abfragen schreiben

Formulieren Sie jeweils eine SQL-Abfrage. Verwenden Sie sprechende Aliase.

  1. Ermitteln Sie alle Alben, deren Tracks zu mindestens zwei unterschiedlichen Genres gehören und deren Gesamtspielzeit länger ist als die durchschnittliche Gesamtspielzeit aller Alben. Auszugeben sind Künstlername, Albumtitel, Anzahl Genres und Gesamtminuten. 5 Punkte
  2. Ermitteln Sie alle Kunden, für die gilt: Jede bezahlte Rechnung des Kunden hat mindestens zwei Rechnungspositionen. Kunden ohne bezahlte Rechnung sollen nicht erscheinen. 5 Punkte
  3. Ermitteln Sie pro Genre die Anzahl der Tracks, die noch nie verkauft wurden, aber in mindestens einer Playlist enthalten sind. Genres ohne solche Tracks sollen mit 0 erscheinen. 4 Punkte
  4. Ermitteln Sie die fünf umsatzstärksten Alben des Jahres 2025. Es zählen nur bezahlte Rechnungen. Auszugeben sind Künstler, Album, Umsatz und Anzahl verkaufter Tracks. 4 Punkte

8 PunkteAufgabe 4: Views, Index, Rechte und Systemkatalog

  1. Erstellen Sie eine View v_paid_album_revenue, die je Künstler und Album den Umsatz bezahlter Rechnungen aggregiert. 3 Punkte
  2. Ist diese View in PostgreSQL direkt änderbar? Begründen Sie kurz. 2 Punkte
  3. Schlagen Sie zwei sinnvolle Indizes für häufige Auswertungen nach invoice.status, invoice.invoice_date und Join auf invoice_line.track_id vor. Begründen Sie jeweils knapp. 2 Punkte
  4. Schreiben Sie eine Abfrage gegen information_schema, die die Spalten der Tabelle invoice_line mit Datentypen ausgibt. 1 Punkt

16 PunkteAufgabe 5: Function und Trigger in PL/pgSQL

Die Spalte invoice.total soll automatisch aus den Positionen in invoice_line gepflegt werden. Außerdem dürfen Positionen einer bereits bezahlten Rechnung nicht mehr geändert oder gelöscht werden.

  1. Schreiben Sie eine Function recalc_invoice_total(p_invoice_id integer), die den Rechnungsbetrag neu berechnet, in invoice.total schreibt und den neuen Betrag zurückgibt. 5 Punkte
  2. Schreiben Sie eine Trigger-Function, die bei INSERT, UPDATE und DELETE auf invoice_line die betroffenen Rechnungen neu berechnet. Bei UPDATE ist auch der Fall zu behandeln, dass sich invoice_id ändert. 7 Punkte
  3. Legen Sie den Trigger an. 2 Punkte
  4. Nennen Sie eine Nebenläufigkeitsgrenze dieser Lösung und wie man sie entschärfen kann. 2 Punkte

12 PunkteAufgabe 6: Transaktionen, Isolation und Commit-Protokolle

Betrachten Sie die folgende Situation. Ein Trigger aus Aufgabe 5 aktualisiert invoice.total nach jeder Änderung an invoice_line.

-- T1
BEGIN;
SELECT total FROM invoice WHERE invoice_id = 42; -- Ergebnis: 100.00

-- T2
BEGIN;
INSERT INTO invoice_line(invoice_id, line_no, track_id, quantity, unit_price)
VALUES (42, 5, 77, 1, 25.00);
COMMIT;

-- T1
SELECT total FROM invoice WHERE invoice_id = 42;
COMMIT;
  1. Welches Ergebnis kann die zweite Abfrage von T1 bei READ COMMITTED liefern, welches bei REPEATABLE READ? Benennen Sie die Anomalie bzw. den Isolationseffekt. 4 Punkte
  2. Wie kann T1 die Rechnung so lesen, dass parallele Änderungen blockiert werden, solange T1 arbeitet? 2 Punkte
  3. Skizzieren Sie einen Deadlock mit zwei Transaktionen, die zwei Rechnungen in unterschiedlicher Reihenfolge sperren. Wie vermeidet man ihn praktisch? 3 Punkte
  4. Grenzen Sie 2PL und 2PC ab. Warum lösen sie unterschiedliche Probleme? 3 Punkte

12 PunkteAufgabe 7: JDBC und JPA

Ein Java-Programm soll alle Tracks eines Genres um einen Faktor verteuern und die Aktion protokollieren. Ergänzen Sie den Code konzeptionell sauber. Es genügt eine kompakte, syntaktisch plausible Lösung.

Connection conn = DriverManager.getConnection(url, props);
String genre = args[0];
BigDecimal factor = new BigDecimal(args[1]);

// START
// UPDATE track ...
// INSERT INTO price_log(genre_name, factor, changed_at) ...
// ENDE
  1. Schreiben Sie den JDBC-Code zwischen START und ENDE mit PreparedStatement, expliziter Transaktion, Commit/Rollback und Ressourcenfreigabe. 7 Punkte
  2. Nennen Sie zwei konkrete Fehler, die bei einer Lösung mit Statement und String-Konkatenation entstehen können. 2 Punkte
  3. Ordnen Sie in JPA die Begriffe transient, managed, detached und removed einem Entity-Lebenszyklus zu. 3 Punkte

12 PunkteAufgabe 8: Verteilte Datenbanken und NoSQL

Die Musikplattform wird erweitert. Es entstehen folgende Anforderungen:

  • Sitzungs- und Feature-Flag-Daten sollen extrem schnell gelesen und mit kurzer Lebensdauer gespeichert werden.
  • Künstlerprofile enthalten je nach Künstler sehr unterschiedliche verschachtelte Metadaten.
  • Hörereignisse werden millionenfach pro Tag geschrieben und meist nach user_id und Tag gelesen.
  • Empfehlungen sollen Beziehungsmuster zwischen Künstlern, Playlists und Kunden auswerten.
  • Bezahlte Rechnungen bleiben ACID-kritisch.
  1. Ordnen Sie Redis, MongoDB, Cassandra, Neo4j und das relationale System jeweils passend zu. Begründen Sie die wichtigste Entscheidung pro System. 5 Punkte
  2. Schreiben Sie eine MongoDB-Abfrage, die aktive Künstlerprofile mit Tag live findet und nur name und metadata.tourYear ausgibt. 2 Punkte
  3. Entwerfen Sie eine Cassandra-Tabelle für Hörereignisse pro Nutzer und Tag. Markieren Sie Partition Key und Clustering Columns und nennen Sie eine nicht unterstützte Abfrageart. 3 Punkte
  4. Schreiben Sie eine Cypher-Abfrage, die Kunden findet, die eine Playlist mit einem Künstler teilen, dessen Name Metallica ist. 2 Punkte

8 PunkteAufgabe 9: Multiple Choice

Kreuzen Sie für jede Aussage an, ob sie richtig oder falsch ist. Jede Zeile zählt 1 Punkt.

Nr. Aussage Richtig Falsch
1Ein Primärschlüssel erzeugt in PostgreSQL eine Eindeutigkeitsabsicherung, typischerweise über einen Index.
2Aggregatbedingungen wie COUNT(*) > 1 gehören in die WHERE-Klausel, weil WHERE vor GROUP BY ausgeführt wird.
3Ein Filter auf der rechten Tabelle eines LEFT JOIN kann in der WHERE-Klausel den OUTER JOIN faktisch zum INNER JOIN machen.
4Eine PostgreSQL-Function ist der richtige Ort, um beliebig COMMIT und ROLLBACK auszuführen.
5PreparedStatement schützt auch dann vollständig vor SQL Injection, wenn Tabellen- oder Spaltennamen ungeprüft aus Benutzereingaben konkateniert werden.
6REPEATABLE READ arbeitet in PostgreSQL mit einem stabilen Snapshot, ist aber nicht identisch mit allgemeiner Serialisierbarkeit.
7Cassandra eignet sich besonders gut für beliebige Ad-hoc-Joins über mehrere Tabellen.
8BASE akzeptiert im Vergleich zu strengem ACID eher temporäre Inkonsistenzen, um Verfügbarkeit und Skalierbarkeit zu begünstigen.

Musterlösung

Die folgenden Lösungen zeigen jeweils eine mögliche robuste Lösung. Gleichwertige Varianten sind korrekt, wenn sie die fachlichen Bedingungen einhalten.

Lösung 1: DDL und Integrität

CREATE TABLE artist_contract (
  contract_id integer GENERATED ALWAYS AS IDENTITY,
  artist_id integer NOT NULL,
  label_name varchar(80) NOT NULL,
  starts_on date NOT NULL,
  ends_on date,
  royalty_rate numeric(5,4) NOT NULL,
  exclusive boolean NOT NULL DEFAULT true,

  CONSTRAINT pk_artist_contract
    PRIMARY KEY (contract_id),
  CONSTRAINT fk_artist_contract_artist
    FOREIGN KEY (artist_id)
    REFERENCES artist (artist_id)
    ON DELETE CASCADE,
  CONSTRAINT ck_artist_contract_dates
    CHECK (ends_on IS NULL OR ends_on >= starts_on),
  CONSTRAINT ck_artist_contract_royalty
    CHECK (royalty_rate > 0 AND royalty_rate <= 0.5),
  CONSTRAINT uq_artist_contract_label_start
    UNIQUE (artist_id, label_name, starts_on)
);
ALTER TABLE album
  ADD CONSTRAINT uq_album_artist_title
  UNIQUE (artist_id, title);

Risiko: Der Befehl scheitert, wenn Bestandsdaten bereits doppelte Kombinationen aus artist_id und title enthalten. Außerdem kann das Sperren der Tabelle im Produktivbetrieb stören.

InsertBewertung
1Zulässig, wenn noch kein gleicher Vertrag existiert. exclusive erhält den Default true.
2Nicht zulässig, weil der Fremdschlüssel auf artist_id = 999 verletzt wird.
3Nicht zulässig: ends_on liegt vor starts_on und royalty_rate = 0.60 verletzt den CHECK.

Lösung 2: Abfrageergebnis

landrechnungenjoin_zeilenpaid_total
FR1135.00
DE3320.00
?010

Der Filter auf CANCELLED steht in der ON-Bedingung, deshalb bleiben Kunden ohne passende Rechnung beim LEFT JOIN erhalten; in der WHERE-Klausel würden die NULL-erweiterten Zeilen herausfallen.

Lösung 3: SQL-Abfragen

-- 1) Alben mit mindestens zwei Genres und überdurchschnittlicher Gesamtspielzeit
WITH album_lengths AS (
  SELECT
    a.album_id,
    ar.name AS artist_name,
    a.title AS album_title,
    COUNT(DISTINCT t.genre_id) AS genre_count,
    SUM(t.milliseconds) AS total_ms
  FROM album a
  JOIN artist ar ON ar.artist_id = a.artist_id
  JOIN track t ON t.album_id = a.album_id
  GROUP BY a.album_id, ar.name, a.title
)
SELECT
  artist_name,
  album_title,
  genre_count,
  ROUND(total_ms / 60000.0, 2) AS total_minutes
FROM album_lengths
WHERE genre_count >= 2
  AND total_ms > (SELECT AVG(total_ms) FROM album_lengths)
ORDER BY total_minutes DESC;
-- 2) Kunden, deren jede bezahlte Rechnung mindestens zwei Positionen hat
SELECT c.customer_id, c.email
FROM customer c
WHERE EXISTS (
  SELECT 1
  FROM invoice i
  WHERE i.customer_id = c.customer_id
    AND i.status = 'PAID'
)
AND NOT EXISTS (
  SELECT 1
  FROM invoice i
  WHERE i.customer_id = c.customer_id
    AND i.status = 'PAID'
    AND (
      SELECT COUNT(*)
      FROM invoice_line il
      WHERE il.invoice_id = i.invoice_id
    ) < 2
);
-- 3) Pro Genre: nie verkaufte, aber in Playlist enthaltene Tracks
SELECT
  g.name AS genre_name,
  COUNT(t.track_id) AS unsold_playlist_tracks
FROM genre g
LEFT JOIN track t
  ON t.genre_id = g.genre_id
 AND EXISTS (
   SELECT 1
   FROM playlist_track pt
   WHERE pt.track_id = t.track_id
 )
 AND NOT EXISTS (
   SELECT 1
   FROM invoice_line il
   WHERE il.track_id = t.track_id
 )
GROUP BY g.genre_id, g.name
ORDER BY g.name;
-- 4) Top-5-Alben nach Umsatz im Jahr 2025
SELECT
  ar.name AS artist_name,
  a.title AS album_title,
  SUM(il.quantity * il.unit_price) AS revenue,
  SUM(il.quantity) AS sold_tracks
FROM invoice i
JOIN invoice_line il ON il.invoice_id = i.invoice_id
JOIN track t ON t.track_id = il.track_id
JOIN album a ON a.album_id = t.album_id
JOIN artist ar ON ar.artist_id = a.artist_id
WHERE i.status = 'PAID'
  AND i.invoice_date >= DATE '2025-01-01'
  AND i.invoice_date < DATE '2026-01-01'
GROUP BY ar.artist_id, ar.name, a.album_id, a.title
ORDER BY revenue DESC, sold_tracks DESC, artist_name, album_title
LIMIT 5;

Lösung 4: Views, Index, Rechte und Systemkatalog

CREATE OR REPLACE VIEW v_paid_album_revenue AS
SELECT
  ar.artist_id,
  ar.name AS artist_name,
  a.album_id,
  a.title AS album_title,
  SUM(il.quantity * il.unit_price) AS revenue
FROM invoice i
JOIN invoice_line il ON il.invoice_id = i.invoice_id
JOIN track t ON t.track_id = il.track_id
JOIN album a ON a.album_id = t.album_id
JOIN artist ar ON ar.artist_id = a.artist_id
WHERE i.status = 'PAID'
GROUP BY ar.artist_id, ar.name, a.album_id, a.title;

Die View ist nicht direkt änderbar, weil sie Joins, Aggregation und GROUP BY enthält. Änderungen müssten über Basistabellen oder über einen passenden INSTEAD OF-Trigger laufen.

CREATE INDEX ix_invoice_status_date
  ON invoice (status, invoice_date);

CREATE INDEX ix_invoice_line_track
  ON invoice_line (track_id);

Der erste Index unterstützt Filter nach Status und Zeitraum, der zweite Index unterstützt Joins und Existenzprüfungen von Track zu Rechnungspositionen.

SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'invoice_line'
ORDER BY ordinal_position;

Lösung 5: Function und Trigger

CREATE OR REPLACE FUNCTION recalc_invoice_total(p_invoice_id integer)
RETURNS numeric
LANGUAGE plpgsql
AS $$
DECLARE
  v_total numeric(10,2);
BEGIN
  SELECT COALESCE(SUM(quantity * unit_price), 0)
    INTO v_total
  FROM invoice_line
  WHERE invoice_id = p_invoice_id;

  UPDATE invoice
     SET total = v_total
   WHERE invoice_id = p_invoice_id;

  RETURN v_total;
END;
$$;
CREATE OR REPLACE FUNCTION trg_invoice_line_recalc()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
  IF TG_OP IN ('UPDATE', 'DELETE') AND EXISTS (
    SELECT 1
    FROM invoice
    WHERE invoice_id = OLD.invoice_id
      AND status = 'PAID'
  ) THEN
    RAISE EXCEPTION 'Paid invoice % must not be changed', OLD.invoice_id;
  END IF;

  IF TG_OP IN ('INSERT', 'UPDATE') THEN
    IF NEW.quantity <= 0 OR NEW.unit_price < 0 THEN
      RAISE EXCEPTION 'Invalid invoice line values';
    END IF;

    IF EXISTS (
      SELECT 1
      FROM invoice
      WHERE invoice_id = NEW.invoice_id
        AND status = 'PAID'
    ) THEN
      RAISE EXCEPTION 'Paid invoice % must not be changed', NEW.invoice_id;
    END IF;
  END IF;

  IF TG_OP IN ('UPDATE', 'DELETE') THEN
    PERFORM recalc_invoice_total(OLD.invoice_id);
  END IF;

  IF TG_OP IN ('INSERT', 'UPDATE') THEN
    PERFORM recalc_invoice_total(NEW.invoice_id);
    RETURN NEW;
  END IF;

  RETURN OLD;
END;
$$;
CREATE TRIGGER invoice_line_recalc_after_change
AFTER INSERT OR UPDATE OR DELETE ON invoice_line
FOR EACH ROW
EXECUTE FUNCTION trg_invoice_line_recalc();

Nebenläufigkeitsgrenze: Zwei parallele Änderungen derselben Rechnung können sich gegenseitig überholen oder unerwartet blockieren. Praktisch entschärft man das durch klare Transaktionsgrenzen und Sperren der Rechnungszeile, z. B. SELECT ... FOR UPDATE auf invoice, bevor Positionen geändert werden.

Lösung 6: Transaktionen

Bei READ COMMITTED kann T1 beim zweiten Lesen 125.00 sehen, weil T2 inzwischen committet hat und der Trigger invoice.total aktualisiert hat. Das ist ein Non-Repeatable Read auf der Rechnungszeile; liest T1 direkt die Positionen per Aggregat, entspricht der neu auftauchende Datensatz zusätzlich einem Phantom-Effekt.

Bei REPEATABLE READ arbeitet T1 mit einem stabilen Snapshot und sieht beim zweiten Lesen weiterhin 100.00, solange T1 nur liest. Für blockierendes Lesen:

BEGIN;
SELECT total
FROM invoice
WHERE invoice_id = 42
FOR UPDATE;

Deadlock-Beispiel: T1 sperrt Rechnung 1 und wartet danach auf Rechnung 2; T2 sperrt Rechnung 2 und wartet danach auf Rechnung 1. Vermeidung: alle Transaktionen sperren Ressourcen in derselben Reihenfolge, halten Sperren kurz und behandeln Deadlock-Fehler mit Retry.

2PL ist ein Sperrprotokoll für Isolation: Sperren werden in einer Wachstumsphase erworben und in einer Schrumpfungsphase freigegeben. 2PC ist ein Commit-Protokoll für verteilte Transaktionen: Coordinator fragt Teilnehmer in der Prepare-Phase und entscheidet danach Commit oder Rollback. 2PL verhindert Nebenläufigkeitsanomalien, 2PC koordiniert atomaren Abschluss über mehrere Systeme.

Lösung 7: JDBC und JPA

String updateSql = """
    UPDATE track t
       SET unit_price = unit_price * ?
      FROM genre g
     WHERE g.genre_id = t.genre_id
       AND g.name = ?
    """;
String logSql = """
    INSERT INTO price_log(genre_name, factor, changed_at)
    VALUES (?, ?, CURRENT_TIMESTAMP)
    """;

try {
    conn.setAutoCommit(false);

    try (PreparedStatement update = conn.prepareStatement(updateSql);
         PreparedStatement log = conn.prepareStatement(logSql)) {
        update.setBigDecimal(1, factor);
        update.setString(2, genre);
        int changedRows = update.executeUpdate();

        log.setString(1, genre);
        log.setBigDecimal(2, factor);
        log.executeUpdate();

        if (changedRows == 0) {
            throw new SQLException("No tracks changed for genre: " + genre);
        }
    }

    conn.commit();
} catch (SQLException ex) {
    conn.rollback();
    throw ex;
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

Fehler bei Statement und String-Konkatenation: SQL Injection über genre und fehlerhafte Typ-/Formatbehandlung bei Zahlen oder Strings. Außerdem gehen Ausführungsplan-Wiederverwendung und klare Parameterbindung verloren.

JPA-ZustandBedeutung
transientObjekt existiert nur in Java, ist noch nicht mit dem Persistence Context verbunden.
managedObjekt wird vom EntityManager verwaltet; Änderungen werden innerhalb der Transaktion synchronisiert.
detachedObjekt war persistent, ist aber nicht mehr an einen aktiven Persistence Context gebunden.
removedObjekt ist zum Löschen markiert und wird beim Flush/Commit aus der Datenbank entfernt.

Lösung 8: Verteilte Datenbanken und NoSQL

AnforderungPassendes SystemBegründung
Sitzungen und Feature FlagsRedisKey-Value-Zugriff, sehr geringe Latenz, TTL und einfache Datenstrukturen.
Flexible KünstlerprofileMongoDBDokumentmodell passt zu variierenden verschachtelten Metadaten.
Hörereignisse nach Nutzer und TagCassandraHoher Write-Durchsatz und Query-First-Modell über Partition Key.
BeziehungsmusterNeo4jGraphmodell macht Beziehungen und Pfadmuster direkt abfragbar.
Bezahlte RechnungenRelationales DBMSACID, Constraints, Transaktionen und referentielle Integrität sind zentral.
db.artist_profiles.find(
  { active: true, tags: "live" },
  { _id: 0, name: 1, "metadata.tourYear": 1 }
);
CREATE TABLE listen_events_by_user_day (
  user_id uuid,
  event_day date,
  listened_at timestamp,
  track_id uuid,
  artist_id uuid,
  seconds_played int,
  PRIMARY KEY ((user_id, event_day), listened_at, track_id)
) WITH CLUSTERING ORDER BY (listened_at DESC, track_id ASC);

Partition Key: (user_id, event_day). Clustering Columns: listened_at, track_id. Nicht passend ist z. B. eine beliebige Suche nach artist_id über alle Nutzer und Tage ohne eigene Tabelle oder Sekundärmodellierung.

MATCH (c:Customer)-[:USES]->(p:Playlist)-[:CONTAINS]->(:Track)
      -[:PERFORMED_BY]->(a:Artist {name: 'Metallica'})
RETURN DISTINCT c.customer_id, c.email
ORDER BY c.email;

Lösung 9: Multiple Choice

Nr.Richtig/FalschKurzbegründung
1RichtigPrimärschlüssel sichern Eindeutigkeit und werden in PostgreSQL indexgestützt umgesetzt.
2FalschAggregatbedingungen gehören nach der Gruppierung in HAVING.
3RichtigEin WHERE-Filter auf rechte Spalten entfernt NULL-erweiterte Zeilen.
4FalschTransaktionssteuerung gehört nicht beliebig in Functions; Procedures haben engere Sonderfälle.
5FalschParameter schützen Werte, aber keine ungeprüft konkatenierten Objektbezeichner.
6RichtigPostgreSQL nutzt einen stabilen Snapshot; Serialisierbarkeit ist ein stärkerer Level.
7FalschCassandra ist query-orientiert und nicht für beliebige Joins gebaut.
8RichtigBASE steht für weichere Konsistenzmodelle und eventual consistency.

Bewertungsschlüssel

BereichPunkte
DDL und Integrität14
Abfrageergebnis10
SQL-Abfragen schreiben18
Views, Index, Rechte, Systemkatalog8
Function und Trigger16
Transaktionen12
JDBC und JPA12
NoSQL und verteilte Datenbanken12
Multiple Choice8
Gesamt110

Verwendete Quellenbereiche

Quelle Verwendete Schwerpunkte
Beispiele Klausuraufgaben.pdfAufgabentypen: DDL nach Schema, Abfrageergebnis, Multiple Choice, SQL-Abfragen, Functions/Trigger, JDBC-Code und Wissensfragen zu anderen Datenbankkonzepten.
1 - EinführungDatenbankbegriffe, DBMS, ANSI-SPARC, Rollen und praktische Aufgabenprofile.
2 - SQL Teil 1 DDL/DMLPostgreSQL, DBeaver, Datentypen, Constraints, referentielle Integrität, INSERT/UPDATE/DELETE.
3 - SQL Teil 2 DQLSELECT-Reihenfolge, Aggregation, WHERE/HAVING, JOINs, Subqueries, Mengenoperationen und komplexe Abfragen.
4 - SQL Teil 3 ZusatzthemenViews, Updatefähigkeit, Indizes, Rechteverwaltung und Information Schema.
5 - DB-seitige Programmierung und TransaktionenSQL-/PL/pgSQL-Functions, Procedures, Trigger, ACID, Isolation, Locks, Deadlocks, 2PL und 2PC.
6 - JDBC und JPAJDBC-Ablauf, Prepared Statements, Transaktionssteuerung, SQL Injection, REST/3-Schichten, ORM und Entity-Lebenszyklus.
7 - Erweiterungen RDBMS und verteilte DBOLTP/OLAP, Column Store, Code Pushdown, verteilte Datenbanken, CAP und BASE.
8 - NoSQL DatenbankenRedis, MongoDB, Cassandra, Neo4j, Polystores, Einsatzszenarien und Konsistenzentscheidungen.