Übungsaufgaben zu INSERT, UPDATE und DELETE

https://bildung.social/@oerinformatik/

https://oer-informatik.de/sql-dml-uebungsaufgaben

SQL, DML, INSERT, UPDATE, DELETE, Fremdschlüssel, Transaktion, MariaDB, Datenmigration, referentielle Integrität, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (Bearbeitungszeit insgesamt 201 min, mehrere Aufgaben): Sechs Übungsaufgaben zum Einfügen, Ändern und Löschen von Datensätzen – mit Preisänderungen, Fallunterscheidungen, Archivtabellen, Fremdschlüsseln, Transaktionen, Anonymisierung, Datenübernahme in ein neues Tabellenschema, Terminverschiebungen und typischen Fehlern. Alle Lösungen sind ausgeblendet und mit Punkteverteilung versehen [AI-Assisted]. (Zuletzt geändert am 28.10.2026)

Dieser Artikel ist Teil einer Artikelreihe über die SQL-DML: Datensätze einfügen (INSERT) / Datensätze ändern (UPDATE) / Datensätze löschen (DELETE) / Übungsaufgaben zu INSERT, UPDATE und DELETE

Hinweise zur Bearbeitung: Als FIA, FIDP, ISE oder IKD musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1, Aufgabe 2.2, Aufgabe 2.3 und Aufgabe 2.4. Von IKD wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1 und Aufgabe 3.2) keine Probleme bereiten. Die Teilaufgaben einer Aufgabe bauen aufeinander auf: Jede Anweisung wirkt auf den Datenbestand, den die vorherigen Teilaufgaben hinterlassen haben. Schreib Deine Lösung zuerst vollständig auf, bevor Du die Beispiellösung aufklappst. Die SQL-Anweisungen sind in MariaDB-Notation formuliert; andere korrekte Lösungen sind ebenfalls richtig. „Heute“ ist in allen Aufgaben der 18.10.2026. Am Ende findest Du eine SQL-Kurzreferenz – Teilaufgaben, die darauf verweisen, setzen Funktionen voraus, die Du nicht auswendig kennen musst.

Aufgabe 2.1: Sortimentspflege im Fahrrad-Onlineshop (Kernkompetenz)

(ca. 33 min Bearbeitungszeit)

Du bist Auszubildende*r bei der CSB-Rad GmbH in Bielefeld, die Fahrräder und Komponenten über einen Onlineshop verkauft. Bastian Ludwig (Werkstattleitung und Einkauf) bittet Dich, das Sortiment in der Shop-Datenbank zu pflegen. Die Tabelle artikel ist so definiert (Auszug):

CREATE TABLE artikel (
  artikel_id      INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  artikelnr       VARCHAR(12)  NOT NULL UNIQUE,
  bezeichnung     VARCHAR(100) NOT NULL,
  kategorie_id    INT UNSIGNED NOT NULL,
  netto_preis     DECIMAL(10,2) NOT NULL,
  mwst_satz       DECIMAL(4,2) NOT NULL DEFAULT 19.00,
  lagerbestand    INT          NOT NULL DEFAULT 0,
  mindestbestand  INT          NOT NULL DEFAULT 0,
  gewicht_kg      DECIMAL(6,2) NULL,
  konfigurierbar  BOOLEAN      NOT NULL DEFAULT FALSE,
  aktiv           BOOLEAN      NOT NULL DEFAULT TRUE,
  CONSTRAINT chk_preis CHECK (netto_preis >= 0),
  CONSTRAINT fk_art_kategorie FOREIGN KEY (kategorie_id) REFERENCES kategorie (kategorie_id)
);

Weitere Tabellen (Primärschlüssel unterstrichen, Fremdschlüssel mit #):

  • kategorie (kategorie_id, bezeichnung, #ober_kategorie_id)

  • bestellposition (#bestellung_id, pos_nr, #artikel_id, menge, einzelpreis_netto, rabatt_prozent, …) – der Fremdschlüssel artikel_id ist ohne ON DELETE-Regel definiert.

Tabelle kategorie
kategorie_id bezeichnung ober_kategorie_id
1 Fahrräder NULL
2 E-Bikes 1
3 Lastenräder 1
4 Stadträder 1
5 Komponenten NULL
6 Rahmen 5
7 Schlösser 5
8 Beleuchtung 5
9 Bremsen 5
Tabelle artikel (Auszug, alle Artikel mit 19 % MwSt.)
artikel_id artikelnr bezeichnung kategorie_id netto_preis lagerbestand mindestbestand gewicht_kg aktiv
1 RAD-CITY-01 Stadtrad „Promenade“ 7-Gang 4 671.43 12 5 16.80 TRUE
2 RAD-EB-02 E-Bike „Kanal“ Mittelmotor 500 Wh 2 2520.17 4 3 24.50 TRUE
3 RAD-LR-03 Lastenrad „Long John“ individuell 3 3780.67 0 0 38.00 TRUE
4 RAH-IND-01 Rahmen nach Maß (Stahl, pulverbeschichtet) 6 840.34 0 0 2.40 TRUE
5 SCH-BUE-01 Bügelschloss, gleichschließend 7 62.18 0 0 1.30 TRUE
6 SCH-RAH-01 Rahmenschloss, gleichschließend 7 41.18 0 0 0.45 TRUE
7 LIC-SET-01 Lichtset Nabendynamo StVZO 8 75.63 48 20 0.35 TRUE
8 BRE-HYD-01 Hydraulische Scheibenbremse (Paar) 9 133.61 9 10 0.75 TRUE
9 RAD-EB-01 E-Bike „Altstadt“ Nabenmotor (Auslauf) 2 1595.80 1 0 23.10 FALSE

Artikel 9 wurde im Oktober 2026 einmal bestellt; die Bestellung wurde storniert, die Bestellposition ist aber noch gespeichert.

a) Bevor es losgeht, möchte Bastian wissen, welche Befehle Du gleich brauchst und welche eher in die Hände der Datenbankadministration gehören. Ordne in der Tabelle jeder Anweisung die Sprachfamilie (DDL, DML, DQL oder DCL) und die passende CRUD-Operation zu und gib an, ob sie die Struktur oder den Inhalt der Datenbank verändert. (4 Punkte)

Zuordnung der SQL-Anweisungen
Anweisung Sprachfamilie CRUD-Operation verändert …
INSERT INTO kategorie …
SELECT … FROM artikel …
UPDATE artikel SET …
DELETE FROM artikel WHERE …
CREATE TABLE artikel_archiv …
ALTER TABLE artikel MODIFY …
TRUNCATE TABLE import_haendler
GRANT SELECT ON csbrad.artikel TO 'shop_web'@'%'
Anweisung Sprachfamilie CRUD-Operation verändert …
INSERT INTO kategorie … DML Create Inhalt
SELECT … FROM artikel … DQL (im SQL-Standard: DML) Read nichts
UPDATE artikel SET … DML Update Inhalt
DELETE FROM artikel WHERE … DML Delete Inhalt
CREATE TABLE artikel_archiv … DDL Create (Struktur) Struktur
ALTER TABLE artikel MODIFY … DDL Update (Struktur) Struktur
TRUNCATE TABLE import_haendler DDL Delete (alle Zeilen) Inhalt (alle Zeilen auf einmal; technisch wird die Tabelle neu angelegt, der AUTO_INCREMENT-Zähler beginnt neu)
GRANT SELECT ON … TO … DCL keine (Rechtevergabe) Zugriffsrechte

Bewertung: je Zeile 0,5 P, wenn Sprachfamilie, CRUD-Operation und die Angabe „verändert …“ stimmen. Bei SELECT ist „DML“ mit Hinweis auf den SQL-Standard ebenfalls richtig.

b) Die Werkstatt verkauft künftig zwei Verschleißteile auch im Shop. Dafür wird zuerst eine neue Kategorie „Antrieb“ als Unterkategorie von „Komponenten“ benötigt. Füge die Kategorie und anschließend die beiden Artikel mit möglichst wenigen Anweisungen ein. Die Schlüssel sollen von der Datenbank vergeben werden; wo die Tabellendefinition einen passenden Standardwert vorsieht, soll dieser gelten. Das Gewicht der Kette ist noch nicht bekannt. (5 Punkte)

Neue Artikel
Artikelnr. Bezeichnung Kategorie Nettopreis Lagerbestand Mindestbestand Gewicht
BRE-BEL-02 Bremsbelag-Satz Scheibenbremse Bremsen 18,40 € 30 10 0,10 kg
KET-11-E Kette 11-fach E-Bike Antrieb (neu) 32,90 € 15 5 unbekannt
INSERT INTO kategorie (bezeichnung, ober_kategorie_id)
VALUES ('Antrieb', 5);

INSERT INTO artikel (artikelnr, bezeichnung, kategorie_id, netto_preis,
                     lagerbestand, mindestbestand, gewicht_kg)
VALUES ('BRE-BEL-02', 'Bremsbelag-Satz Scheibenbremse', 9, 18.40, 30, 10, 0.10),
       ('KET-11-E', 'Kette 11-fach E-Bike',
        (SELECT kategorie_id FROM kategorie WHERE bezeichnung = 'Antrieb'),
        32.90, 15, 5, NULL);

Ergebnis: Die Kategorie erhält die kategorie_id 10, die Artikel die artikel_id 10 und 11. mwst_satz (19.00), konfigurierbar (FALSE) und aktiv (TRUE) kommen aus den DEFAULT-Werten.

artikel_id artikelnr kategorie_id netto_preis mwst_satz gewicht_kg konfigurierbar aktiv
10 BRE-BEL-02 9 18.40 19.00 0.10 0 1
11 KET-11-E 10 32.90 19.00 NULL 0 1

Bewertung: INSERT der Kategorie mit Spaltenliste ohne Schlüssel 1 P; Spaltenliste beim Artikel ohne artikel_id und ohne Spalten mit passendem Standardwert 1 P; mehrere Zeilen in einer Anweisung 1 P; Fremdschlüssel der neuen Kategorie per Unterabfrage oder LAST_INSERT_ID() 1 P; Literale korrekt (Text in Hochkommata, Dezimalpunkt, NULL ohne Hochkommata) 1 P.

Alternativen: gewicht_kg weglassen (wird ebenfalls NULL). Statt der Unterabfrage ist LAST_INSERT_ID() in der Zeile der Kette ebenso richtig, wenn das Artikel-INSERT unmittelbar auf das Einfügen der Kategorie folgt: Die gerade laufende Anweisung verändert den Wert von LAST_INSERT_ID() nicht, er bleibt während des ganzen INSERT bei 10. Die feste Zahl 10 ist nur richtig, wenn man vorher geprüft hat, welcher Wert vergeben wurde (0,5 P Abzug ohne Begründung).

c) Der Schlosshersteller erhöht zum 1. November die Einkaufspreise; CSB-Rad gibt das weiter. Erhöhe die Nettopreise aller Artikel der Kategorie „Schlösser“ um 6 % und runde auf ganze Cent. Die Kategorie soll in der Anweisung über ihre Bezeichnung, nicht über ihre Nummer ausgewählt werden. Gib die neuen Preise an. (4 Punkte)

UPDATE artikel
SET netto_preis = ROUND(netto_preis * 1.06, 2)
WHERE kategorie_id = (SELECT kategorie_id
                      FROM kategorie
                      WHERE bezeichnung = 'Schlösser');

Neue Preise: Bügelschloss 62,18 € · 1,06 = 65,9108 € → 65,91 €, Rahmenschloss 41,18 € · 1,06 = 43,6508 € → 43,65 €.

Bewertung: UPDATE … SET mit Faktor 1.06 (selbstbezüglich) 1 P, ROUND(…, 2) 1 P, Auswahl über Unterabfrage auf kategorie 1 P, beide Preise 1 P.

Alternative (MariaDB): UPDATE artikel AS a INNER JOIN kategorie AS k ON k.kategorie_id = a.kategorie_id SET a.netto_preis = ROUND(a.netto_preis * 1.06, 2) WHERE k.bezeichnung = 'Schlösser';. Ohne ROUND rundet die Spalte DECIMAL(10,2) beim Speichern selbst (Ergebnis gleich, 0,5 P Abzug, weil die Absicht nicht erkennbar ist). Falsch: netto_preis + 6, netto_preis * 0.06 oder netto_preis * 6 %.

d) Die Tabelle bestellposition speichert einen eigenen Preis einzelpreis_netto. Begründe, warum die Preiserhöhung aus c) die bereits gespeicherten Bestellpositionen nicht verändern darf. (3 Punkte)

  • Vertraglich gilt der Preis zum Zeitpunkt der Bestellung; Kund*innen haben zu diesem Preis gekauft.

  • Rechnungen sind Buchungsbelege und müssen unverändert und nachvollziehbar bleiben (Aufbewahrungspflicht, GoBD); eine spätere Preisänderung darf alte Rechnungsbeträge nicht verfälschen.

  • Auswertungen (z. B. Umsatz eines Quartals) müssen später dieselben Zahlen liefern. Deshalb ist der Preis in der Bestellposition ein bewusst gespeicherter historischer Wert und keine Redundanz.

Im Datenbestand bleiben z. B. Bestellung 1, Position 2 (62,18 €) und Bestellung 5, Position 1 (41,18 €) unverändert.

Bewertung: je tragfähigem Argument 1 P.

e) Für die Komponenten soll ein neuer Mindestbestand gelten: Beleuchtung 25 Stück, Bremsen 12 Stück. Die übrigen Artikel bleiben unverändert. Erstelle eine SQL-Anweisung dafür und gib an, welche Artikel sie ändert. (4 Punkte)

UPDATE artikel
SET mindestbestand = CASE kategorie_id
                       WHEN 8 THEN 25
                       WHEN 9 THEN 12
                     END
WHERE kategorie_id IN (8, 9);

Geändert werden Artikel 7 (Lichtset, 25), Artikel 8 (Scheibenbremse, 12) und der neue Artikel 10 (Bremsbelag-Satz, bisher 10, jetzt 12).

Bewertung: CASE mit zwei Zweigen 2 P, WHERE auf die beiden Kategorien 1 P, betroffene Artikel einschließlich des neuen Artikels 10 1 P.

Ebenso volle Punktzahl: ohne WHERE, dafür mit ELSE mindestbestand (dann werden alle Zeilen durchlaufen, aber nur drei geändert). Ohne WHERE und ohne ELSE setzt das CASE bei allen anderen Artikeln NULL – das scheitert im Strict Mode an NOT NULL, ohne Strict Mode wird mit Warnung 0 gespeichert. Die Suche mit CASE WHEN kategorie_id = 8 THEN … ist ebenso richtig, ebenso zwei getrennte UPDATE-Anweisungen mit 1 P Abzug (nicht „eine Anweisung“).

f) Für eine Herbstaktion soll das Lichtset 10 % günstiger werden. Eine Kollegin hat dazu folgende Anweisung vorbereitet, die aber nicht wie gewünscht funktioniert:

UPDATE artikel
SET netto_preis = netto_preis - 10 %;
WHERE bezeichnung = 'Lichtset'

Nenne die Fehler, erläutere jeweils die Folge und korrigiere die Anweisung. (6 Punkte)

Fehler Folge
- 10 %: Ein Prozentzeichen für „Prozent“ gibt es in SQL nicht, % ist der Modulo-Operator. Syntaxfehler; gemeint ist netto_preis * 0.9 (oder netto_preis - netto_preis * 0.10).
Semikolon vor WHERE Die Anweisung endet vorzeitig: Wäre der Ausdruck gültig, würde der Preis aller Artikel geändert; das folgende WHERE … ist dann eine eigene, ungültige Anweisung.
bezeichnung = 'Lichtset' Die Bezeichnung lautet vollständig „Lichtset Nabendynamo StVZO“ – der Vergleich trifft keine Zeile. Sicherer ist der eindeutige Schlüssel (Artikelnummer oder artikel_id), notfalls LIKE 'Lichtset%'.
keine Rundung Ergebnis 68,067 €; ROUND(…, 2) macht die Rundung auf Cent ausdrücklich.

Korrektur:

UPDATE artikel
SET netto_preis = ROUND(netto_preis * 0.9, 2)
WHERE artikelnr = 'LIC-SET-01';

Ergebnis: 75,63 € · 0,9 = 68,067 € → 68,07 € (1 Zeile geändert).

Bewertung: drei Fehler mit Folge je 1 P (Rundung als vierter Punkt ersatzweise), Korrektur 3 P (Faktor 1 P, WHERE am richtigen Platz mit eindeutiger Bedingung 1 P, Rundung 1 P).

g) Bastian möchte ausgelaufene Artikel aus der Datenbank entfernen und schlägt vor:

DELETE FROM artikel
WHERE aktiv = FALSE;

ga) Erläutere, was beim Ausführen passiert. (2 Punkte)

gb) Erstelle eine Anweisung, die nur solche inaktiven Artikel löscht, die in keiner Bestellposition vorkommen, und nimm Stellung, wie mit Artikel 9 verfahren werden sollte. (4 Punkte)

ga) Die Bedingung trifft Artikel 9. Auf ihn verweist noch eine Bestellposition; der Fremdschlüssel bestellposition.artikel_id hat keine ON DELETE-Regel, es gilt also RESTRICT. MariaDB bricht mit Fehler 1451 ab („Cannot delete or update a parent row: a foreign key constraint fails …“), es wird nichts gelöscht. Die referentielle Integrität verhindert, dass Bestellpositionen auf einen nicht mehr vorhandenen Artikel zeigen. (2 P)

gb)

DELETE FROM artikel
WHERE aktiv = FALSE
  AND NOT EXISTS (SELECT *
                  FROM bestellposition AS bp
                  WHERE bp.artikel_id = artikel.artikel_id);

Ergebnis: 0 Zeilen gelöscht – Artikel 9 bleibt erhalten. Alternative: AND artikel_id NOT IN (SELECT artikel_id FROM bestellposition) (hier unproblematisch, weil artikel_id in bestellposition nie NULL ist).

Stellungnahme: Artikel 9 sollte nicht gelöscht werden, auch nicht über ON DELETE CASCADE (dann verschwänden Bestellpositionen und damit Teile von Bestell- und Rechnungsdaten, die aufbewahrt werden müssen). Er bleibt mit aktiv = FALSE gespeichert und wird im Shop nicht angezeigt; wer die Tabelle entlasten will, kopiert ihn in eine Archivtabelle, ohne den Verweis zu brechen.

Bewertung: DELETE mit NOT EXISTS/NOT IN und korrekter Korrelation 2 P, Ergebnis 1 P, Stellungnahme 1 P.

h) Für eine neue Versandkostenberechnung soll das Gewicht ab sofort bei jedem Artikel angegeben werden. Bis die Werkstatt das Kettengewicht nachgewogen hat, soll für Artikel ohne Gewicht vorläufig 1,00 kg eingetragen werden. Ergänze die fehlenden Werte und mache die Spalte gewicht_kg anschließend zum Pflichtfeld. Begründe die Reihenfolge. (4 Punkte)

UPDATE artikel
SET gewicht_kg = 1.00
WHERE gewicht_kg IS NULL;

ALTER TABLE artikel
MODIFY gewicht_kg DECIMAL(6,2) NOT NULL;

Betroffen ist nur Artikel 11 (Kette). Reihenfolge: Solange noch ein NULL in der Spalte steht, lehnt MariaDB die Änderung auf NOT NULL im Strict Mode (Voreinstellung) mit einem Fehler ab; ohne Strict Mode würde der Wert mit einer Warnung in 0.00 umgewandelt. Deshalb erst die Lücken füllen, dann die Struktur ändern.

Bewertung: UPDATE mit IS NULL 1 P (= NULL trifft keine Zeile: 0 P), ALTER TABLE … MODIFY … NOT NULL mit Datentyp 2 P, Begründung 1 P. CHANGE gewicht_kg gewicht_kg DECIMAL(6,2) NOT NULL ist ebenso richtig.

Aufgabe 2.2: Anmeldungen und Sessionplan eines Barcamps (Kernkompetenz)

(ca. 35 min Bearbeitungszeit)

Am Ratz und Rübe Berufskolleg (RRB) in Bielefeld findet am 13.03.2027 das Barcamp „Lernen.Digital.Leben 2027“ statt. Du gehörst zum Orga-Team von Dimitrios Papadopoulos und pflegst die Orga-Datenbank. Sie enthält unter anderem (Primärschlüssel unterstrichen, Fremdschlüssel mit #):

  • person (person_id, vorname, nachname, email, organisation, geburtsjahr)

  • anmeldung (anmeldung_id, #veranstaltung_id, #person_id, ticketart, angemeldet_am, essen, bedarf_barrierefreiheit, foto_einwilligung, checkin_zeit, badge_code)

  • raum (raum_id, bezeichnung, name, gebaeude, etage, plaetze, beamer, stream_faehig, barrierefrei)

  • slot (slot_id, #veranstaltung_id, beginn, ende)

  • session (session_id, #veranstaltung_id, titel, beschreibung, format, status, eingereicht_am, max_plaetze, #raum_id, #slot_id, livestream, live_untertitel)

  • session_person (#session_id, #person_id, rolle) – Referent*innen und Moderation

  • session_buchung (#session_id, #anmeldung_id, gebucht_am) – Buchungen für Sessions mit begrenzten Plätzen

  • orga_aufgabe (aufgabe_id, titel, #verantwortlich_id, faellig_am, status)

Auszug aus den Tabellendefinitionen:

CREATE TABLE anmeldung (
  anmeldung_id      INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  veranstaltung_id  INT UNSIGNED NOT NULL,
  person_id         INT UNSIGNED NOT NULL,
  ticketart         ENUM('Schueler','Lehrkraft','Extern','Referent','Orga','Aussteller') NOT NULL,
  angemeldet_am     DATETIME     NOT NULL,
  essen             ENUM('keine Angabe','vegetarisch','vegan','ohne Schwein','glutenfrei')
                    NOT NULL DEFAULT 'keine Angabe',
  bedarf_barrierefreiheit VARCHAR(200) NULL,
  foto_einwilligung BOOLEAN      NOT NULL DEFAULT FALSE,
  checkin_zeit      DATETIME     NULL,
  badge_code        CHAR(8)      NULL UNIQUE,
  CONSTRAINT uq_anmeldung UNIQUE (veranstaltung_id, person_id),
  FOREIGN KEY (veranstaltung_id) REFERENCES veranstaltung (veranstaltung_id),
  FOREIGN KEY (person_id)        REFERENCES person (person_id)
);
-- in session:         CONSTRAINT uq_raum_slot UNIQUE (raum_id, slot_id)
-- in session_person:  FOREIGN KEY (session_id)   REFERENCES session (session_id)     ON DELETE CASCADE
-- in session_buchung: FOREIGN KEY (session_id)   REFERENCES session (session_id)     ON DELETE CASCADE,
--                     FOREIGN KEY (anmeldung_id) REFERENCES anmeldung (anmeldung_id) ON DELETE CASCADE
-- in orga_aufgabe:    FOREIGN KEY (verantwortlich_id) REFERENCES person (person_id)
Tabelle raum (Auszug)
raum_id bezeichnung name plaetze barrierefrei
1 A.001 Aula 250 TRUE
2 A.104 NULL 32 TRUE
3 A.105 NULL 32 TRUE
4 B.012 Mensa 180 TRUE
5 B.201 IT-Labor 1 28 TRUE
6 B.202 IT-Labor 2 28 TRUE
7 C.110 Elektrowerkstatt 24 FALSE
8 C.020 Bibliothek 40 TRUE
Tabelle slot (alle Slots gehören zur Veranstaltung 1)
slot_id beginn ende
1 10:00:00 10:45:00
2 11:00:00 11:45:00
3 12:45:00 13:30:00
4 13:45:00 14:30:00
5 14:45:00 15:30:00
Tabelle session (Auszug)
session_id titel format status max_plaetze raum_id slot_id
1 KI-Assistenten im Unterricht – was geht, was nicht? Vortrag angenommen NULL 1 1
2 Reparieren statt wegwerfen: Laptops retten Workshop angenommen 12 7 2
3 Barrierefreie Videos: Untertitel und Gebärdensprache Workshop angenommen 20 5 3
4 Escape-Room mit Mikrocontrollern Workshop angenommen 16 6 4
5 Datenschutz im Schulalltag – Mythen und Fakten Diskussion eingereicht NULL NULL NULL
6 Barrierefreie Webseiten mit HTML und ARIA Tutorial angenommen 24 6 5
7 Krypto-Trading für Einsteiger Vortrag abgelehnt NULL NULL NULL

Bisher sind 14 Personen gespeichert (person_id 1 bis 14), jede mit genau einer Anmeldung (anmeldung_id = person_id). Fatma Öztürk (person_id 6, E-Mail fatma.oeztuerk@rrb-bielefeld.example) hat die Sessions 2, 4 und 6 gebucht; sie ist weder Referentin noch für eine Orga-Aufgabe verantwortlich.

a) Samira Benali von der Senne-Logistik GmbH (E-Mail samira.benali@example.org) hat sich am 18.10.2026 um 10:30 Uhr als Referentin angemeldet. Sie isst vegan, hat keinen Barrierefreiheitsbedarf angegeben und der Veröffentlichung von Fotos nicht zugestimmt. Ihr Badge erhält den Code BC27R015. Erstelle die SQL-Anweisungen, die Person und Anmeldung speichern. Die Schlüssel vergibt die Datenbank. (5 Punkte)

INSERT INTO person (vorname, nachname, email, organisation)
VALUES ('Samira', 'Benali', 'samira.benali@example.org', 'Senne-Logistik GmbH');

INSERT INTO anmeldung (veranstaltung_id, person_id, ticketart, angemeldet_am,
                       essen, badge_code)
VALUES (1, LAST_INSERT_ID(), 'Referent', '2026-10-18 10:30:00',
        'vegan', 'BC27R015');

Ergebnis: person_id 15, anmeldung_id 15; foto_einwilligung erhält den Standardwert FALSE, bedarf_barrierefreiheit, checkin_zeit und geburtsjahr bleiben NULL.

Bewertung: Reihenfolge Person vor Anmeldung (Fremdschlüssel) 1 P, INSERT Person 1 P, INSERT Anmeldung mit Spaltenliste 1 P, Fremdschlüssel per LAST_INSERT_ID() oder Unterabfrage (SELECT person_id FROM person WHERE email = 'samira.benali@example.org') 1 P, Datum/Zeit im ISO-Format und ENUM-Werte richtig geschrieben 1 P. foto_einwilligung = FALSE ausdrücklich anzugeben ist ebenso richtig.

b) Die Diskussion „Datenschutz im Schulalltag“ (Session 5) wurde angenommen. Sie soll im Raum A.105 im Slot ab 11:00 Uhr stattfinden und auf 32 Plätze begrenzt sein. Erstelle eine SQL-Anweisung, die alle Angaben speichert. Raum und Slot sollen über die Raumbezeichnung bzw. die Uhrzeit ausgewählt werden. (4 Punkte)

UPDATE session
SET status      = 'angenommen',
    raum_id     = (SELECT raum_id FROM raum WHERE bezeichnung = 'A.105'),
    slot_id     = (SELECT slot_id FROM slot WHERE beginn = '11:00:00'),
    max_plaetze = 32
WHERE session_id = 5;

Ergebnis: Session 5 hat jetzt status = 'angenommen', raum_id = 3, slot_id = 2, max_plaetze = 32.

Bewertung: mehrere Zuweisungen mit Komma 1 P, zwei Unterabfragen 2 P, WHERE auf den Primärschlüssel 1 P. Bei mehreren Veranstaltungen müsste die Slot-Unterabfrage zusätzlich AND veranstaltung_id = 1 enthalten. Werden die Zuweisungen mit AND verbunden, ist die Anweisung falsch (0,5 P für den Ansatz).

c) Gerhard Vogt leitet den Reparatur-Workshop und ist auf einen Aufzug angewiesen. Dimitrios vermutet, dass Sessions in nicht barrierefreien Räumen geplant sind.

ca) Ermittle mit einer SQL-Abfrage alle angenommenen Sessions, die in einem nicht barrierefreien Raum liegen. (3 Punkte)

cb) Verlege die betroffene Session in das IT-Labor 1 (B.201); der Slot bleibt gleich. Begründe, warum die Änderung nicht gegen die Bedingung uq_raum_slot verstößt. (3 Punkte)

ca)

SELECT s.session_id, s.titel, r.bezeichnung, r.barrierefrei
FROM session AS s
  INNER JOIN raum AS r ON r.raum_id = s.raum_id
WHERE s.status = 'angenommen'
  AND r.barrierefrei = FALSE;
session_id titel bezeichnung barrierefrei
2 Reparieren statt wegwerfen: Laptops retten C.110 0

Bewertung: Verbund 1 P, beide Bedingungen 1 P, Ergebnis 1 P.

cb)

UPDATE session
SET raum_id = (SELECT raum_id FROM raum WHERE bezeichnung = 'B.201')
WHERE session_id = 2;

uq_raum_slot verlangt, dass jede Kombination aus Raum und Slot nur einmal vorkommt. B.201 (Raum 5) ist nur im Slot 3 belegt (Session 3); Session 2 liegt im Slot 2, die Kombination (5, 2) ist frei. Wäre sie belegt, würde MariaDB das UPDATE mit „Duplicate entry“ ablehnen.

Bewertung: UPDATE 2 P (WHERE session_id = 2 oder gleichwertige Bedingung), Begründung 1 P. Der Weg „erst mit SELECT prüfen, dann mit derselben Bedingung ändern“ ist bewusst so gewählt: So sieht man vorher, welche Zeilen betroffen sind.

d) Sessions ohne Platzbegrenzung sollen auf die Zahl der Plätze ihres Raums begrenzt werden. Ein Teammitglied hat dazu mit

SELECT session_id, max_plaetze FROM session WHERE max_plaetze = NULL;

nachgesehen und keine Session gefunden. Erläutere, warum diese Abfrage nie eine Zeile liefert. Erstelle anschließend eine SQL-Anweisung, die bei allen angenommenen Sessions ohne Platzbegrenzung max_plaetze auf die Platzzahl des zugeordneten Raums setzt, und gib an, welche Session sie ändert. (6 Punkte)

NULL steht für „unbekannt“. Ein Vergleich mit NULL (=, <>, < …) ergibt deshalb ebenfalls „unbekannt“ und nie „wahr“ – keine Zeile erfüllt die Bedingung. Leere Felder findet man nur mit IS NULL. (2 P)

UPDATE session
SET max_plaetze = (SELECT r.plaetze
                   FROM raum AS r
                   WHERE r.raum_id = session.raum_id)
WHERE status = 'angenommen'
  AND max_plaetze IS NULL;

Geändert wird nur Session 1 (Aula): max_plaetze = 250. Session 5 hat seit b) bereits 32 Plätze, Session 7 ist abgelehnt.

Bewertung: korrelierte Unterabfrage im SET 2 P, WHERE mit IS NULL und Status 1 P, Ergebnis 1 P.

Alternative (MariaDB): UPDATE session AS s INNER JOIN raum AS r ON r.raum_id = s.raum_id SET s.max_plaetze = r.plaetze WHERE s.status = 'angenommen' AND s.max_plaetze IS NULL;

e) Abgelehnte und zurückgezogene Sessions sollen aus dem Sessionplan verschwinden, für die Dokumentation des Call for Participation aber erhalten bleiben. Lege dazu eine Tabelle session_archiv mit derselben Struktur wie session an, kopiere die betroffenen Sessions hinein und lösche sie anschließend aus session. Erläutere, welche Datensätze in anderen Tabellen beim Löschen mitgelöscht würden. (6 Punkte)

CREATE TABLE session_archiv LIKE session;

INSERT INTO session_archiv
SELECT *
FROM session
WHERE status IN ('abgelehnt', 'zurueckgezogen');

DELETE FROM session
WHERE session_id IN (SELECT session_id FROM session_archiv);

Ergebnis: Session 7 („Krypto-Trading für Einsteiger“) steht im Archiv und nicht mehr in session (6 Sessions verbleiben).

Mitgelöscht würden wegen ON DELETE CASCADE die zugehörigen Zeilen in session_person (Rollen), session_buchung (Buchungen) und – in der vollständigen Datenbank – feedback. Bei Session 7 gibt es keine solchen Zeilen; bei einer angenommenen, später zurückgezogenen Session gingen die Buchungen aber verloren und die Teilnehmenden müssten informiert werden. Die Referent*innen-Zuordnung landet nicht im Archiv – soll sie erhalten bleiben, braucht man auch eine session_person_archiv.

Bewertung: CREATE TABLE … LIKE 1 P (ausgeschriebenes CREATE TABLE mit allen Spalten ebenso), INSERT … SELECT * mit IN 2 P, DELETE mit Bezug auf das Archiv oder derselben Bedingung 2 P, Erläuterung CASCADE 1 P.

SELECT * ist hier zulässig, weil beide Tabellen dieselbe Spaltenreihenfolge haben. Hinweis: CREATE TABLE … LIKE übernimmt Indizes, aber keine Fremdschlüssel; für ein Archiv ist das meist erwünscht. Ebenfalls richtig ist CREATE TABLE session_archiv AS SELECT * FROM session WHERE status IN ('abgelehnt', 'zurueckgezogen'); – es ersetzt CREATE TABLE und INSERT (dann ohne Primärschlüssel und Indizes).

f) Fatma Öztürk hat ihre Teilnahme abgesagt.

fa) Lösche ihre Anmeldung. Die Person soll über ihre E-Mail-Adresse ausgewählt werden. (2 Punkte)

fb) Erläutere, was mit ihren Session-Buchungen geschieht und warum MariaDB trotzdem „1 row affected“ meldet. (2 Punkte)

fc) Aus Gründen der Datenminimierung sollen alle Personen gelöscht werden, die weder eine Anmeldung haben noch als Referent*in oder Moderation eingetragen noch für eine Orga-Aufgabe verantwortlich sind. Erstelle die SQL-Anweisung und gib an, wer gelöscht wird. (4 Punkte)

fa)

DELETE FROM anmeldung
WHERE person_id = (SELECT person_id
                   FROM person
                   WHERE email = 'fatma.oeztuerk@rrb-bielefeld.example');

Bewertung: DELETE FROM … WHERE 1 P, Unterabfrage über die E-Mail-Adresse 1 P (bei mehreren Veranstaltungen zusätzlich AND veranstaltung_id = 1).

fb) Der Fremdschlüssel session_buchung.anmeldung_id ist mit ON DELETE CASCADE definiert: Ihre drei Buchungen (Sessions 2, 4 und 6) werden automatisch mitgelöscht; die Plätze werden frei. MariaDB zählt als betroffene Zeilen nur die der Tabelle, aus der ausdrücklich gelöscht wurde, nicht die kaskadierten Zeilen. (2 P)

fc)

DELETE FROM person
WHERE NOT EXISTS (SELECT * FROM anmeldung AS a
                  WHERE a.person_id = person.person_id)
  AND NOT EXISTS (SELECT * FROM session_person AS sp
                  WHERE sp.person_id = person.person_id)
  AND NOT EXISTS (SELECT * FROM orga_aufgabe AS oa
                  WHERE oa.verantwortlich_id = person.person_id);

Gelöscht wird nur Fatma Öztürk (person_id 6); 14 Personen bleiben.

Bewertung: drei NOT EXISTS- bzw. NOT IN-Bedingungen mit AND 3 P, Ergebnis 1 P. Mit NOT IN (SELECT person_id FROM anmeldung) usw. ist es ebenso richtig, solange die Unterabfragen kein NULL liefern (die Spalten sind hier alle NOT NULL). Ohne die Bedingung zu orga_aufgabe würde das DELETE bei einer Person mit Orga-Aufgabe am Fremdschlüssel scheitern.

g) Erledigte Orga-Aufgaben mit einer Fälligkeit vom 1. September bis einschließlich 31. Oktober 2026 sollen gelöscht werden. Nutze die SQL-Kurzreferenz in der Anlage. (3 Punkte)

DELETE FROM orga_aufgabe
WHERE status = 'erledigt'
  AND faellig_am BETWEEN '2026-09-01' AND '2026-10-31';

Gelöscht wird die Aufgabe „Call for Participation veröffentlichen“ (fällig am 01.10.2026).

Bewertung: Bedingung auf den Status 1 P, BETWEEN mit ISO-Datumswerten 1 P, Verknüpfung mit AND 1 P. BETWEEN schließt beide Grenzen ein; bei faellig_am (Typ DATE) ist das unproblematisch. >= '2026-09-01' AND <= '2026-10-31' ist ebenso richtig; '01.09.2026' ist kein gültiges Datumsliteral.

Aufgabe 2.3: Löschkonzept einer Freizeit-App umsetzen (Kernkompetenz)

(ca. 35 min Bearbeitungszeit)

Du bist Auszubildende*r bei der Let’s Meet GmbH in Bielefeld. Die App „Let’s Meet“ bringt Menschen ab 16 Jahren für gemeinsame Freizeitaktivitäten zusammen. Die externe Datenschutzbeauftragte Dr. Marta Kowalczyk hat ein Löschkonzept beschlossen, das Du zusammen mit Deinem Mit-Azubi Jonas Weber in der MariaDB-Datenbank umsetzt:

  1. Stattgefundene Aktivitäten, deren Beginn mehr als 14 Tage zurückliegt, werden ohne Personenbezug in eine Archivtabelle übernommen (für Statistiken). Danach werden die Aktivität und alle Teilnahmen und Bewertungen dazu gelöscht.

  2. Abgesagte Aktivitäten werden nach 14 Tagen ohne Archiv gelöscht.

  3. Gesperrte Konten, deren letzter Login mehr als 180 Tage zurückliegt, werden anonymisiert.

Auszug aus dem Datenmodell (Primärschlüssel unterstrichen, Fremdschlüssel mit #):

  • benutzer (benutzer_id, vorname, nachname, email, passwort_hash, geburtsdatum, geschlecht, strasse, hausnr, plz, ort, verifiziert, profil_sichtbar, status, registriert_am, letzter_login)

  • hobby (hobby_id, bezeichnung, kategorie)

  • treffpunkt (treffpunkt_id, name, strasse, plz, ort, …)

  • aktivitaet (aktivitaet_id, titel, #hobby_id, #ersteller_id, #treffpunkt_id, beginn, dauer_min, max_teilnehmende, kosten_eur, mindestalter, status, …)

  • teilnahme (#aktivitaet_id, #benutzer_id, angemeldet_am, status) – Status u. a. 'teilgenommen', 'nicht_erschienen', 'abgemeldet'

  • bewertung (bewertung_id, #aktivitaet_id, #bewerter_id, sterne, kommentar, erstellt_am)

  • meldung (meldung_id, #melder_id, #gemeldet_id, #aktivitaet_id, grund, beschreibung, …, ergebnis)

Fremdschlüssel auf aktivitaet: teilnahme und bewertung mit ON DELETE CASCADE, meldung mit ON DELETE SET NULL. Die Spalten strasse, hausnr und geschlecht in benutzer sind optional, status ist ein ENUM('aktiv','gesperrt','geloescht').

Tabelle aktivitaet (Auszug, mit Bezeichnungen aus den verbundenen Tabellen)
aktivitaet_id titel hobby (aus hobby) ort (aus treffpunkt) beginn status
1 Brettspielabend: Kooperative Spiele Brettspiele Bielefeld 2026-09-25 19:00:00 stattgefunden
2 Sonntagswanderung Teutoburger Wald (12 km) Wandern Bielefeld 2026-10-04 10:00:00 stattgefunden
3 Feierabend-Kick Fußball Bielefeld 2026-10-21 18:30:00 geplant
6 Schach im Park Schach Bielefeld 2026-09-27 15:00:00 abgesagt
8 Kochabend: Mezze mit Weinbegleitung Kochen Herford 2026-10-17 18:00:00 stattgefunden

Zu Aktivität 1 gibt es fünf Teilnahmen (vier mit Status 'teilgenommen', eine 'nicht_erschienen'), zwei Bewertungen (5 und 4 Sterne) und eine Meldung. Zu Aktivität 6 gibt es eine Teilnahme mit Status 'abgemeldet'.

Die Archivtabelle hat Marta bereits anlegen lassen:

CREATE TABLE aktivitaet_archiv (
  aktivitaet_id  INT UNSIGNED NOT NULL PRIMARY KEY,
  titel          VARCHAR(100) NOT NULL,
  hobby          VARCHAR(50)  NOT NULL,     -- Bezeichnung als Text
  ort            VARCHAR(60)  NOT NULL,     -- Ort des Treffpunkts
  monat          CHAR(7)      NOT NULL,     -- 'JJJJ-MM'
  teilnehmende   TINYINT UNSIGNED NOT NULL, -- Teilnahmen mit Status 'teilgenommen'
  sterne_schnitt DECIMAL(3,2) NULL          -- NULL, wenn es keine Bewertung gibt
);

a) Erstelle eine SQL-Anweisung, die Regel 1 umsetzt, soweit es das Archivieren betrifft: Alle stattgefundenen Aktivitäten, deren Beginn mehr als 14 Tage vor dem heutigen Tag liegt, sollen mit einer Anweisung in die Archivtabelle übernommen werden. Nutze die SQL-Kurzreferenz in der Anlage. Gib den archivierten Datensatz an. (8 Punkte)

INSERT INTO aktivitaet_archiv (aktivitaet_id, titel, hobby, ort, monat,
                               teilnehmende, sterne_schnitt)
SELECT a.aktivitaet_id,
       a.titel,
       h.bezeichnung,
       tp.ort,
       DATE_FORMAT(a.beginn, '%Y-%m'),
       (SELECT COUNT(*)
        FROM teilnahme AS t
        WHERE t.aktivitaet_id = a.aktivitaet_id
          AND t.status = 'teilgenommen'),
       (SELECT AVG(b.sterne)
        FROM bewertung AS b
        WHERE b.aktivitaet_id = a.aktivitaet_id)
FROM aktivitaet AS a
  INNER JOIN hobby AS h       ON h.hobby_id = a.hobby_id
  INNER JOIN treffpunkt AS tp ON tp.treffpunkt_id = a.treffpunkt_id
WHERE a.status = 'stattgefunden'
  AND a.beginn < CURDATE() - INTERVAL 14 DAY;
aktivitaet_id titel hobby ort monat teilnehmende sterne_schnitt
1 Brettspielabend: Kooperative Spiele Brettspiele Bielefeld 2026-09 4 4.50

CURDATE() - INTERVAL 14 DAY ist der 04.10.2026 (00:00 Uhr). Die Wanderung (Aktivität 2) beginnt am 04.10.2026 um 10:00 Uhr und ist damit noch nicht älter als 14 Tage.

Bewertung: INSERT INTO … (Spaltenliste) SELECT 1 P, Verbund mit hobby und treffpunkt 1 P, DATE_FORMAT (oder CONCAT(YEAR(…), '-', LPAD(MONTH(…), 2, '0'))) 1 P, Teilnehmende mit Statusbedingung 1 P, Durchschnitt der Sterne 1 P, Statusbedingung 1 P, Datumsbedingung 1 P, Ergebniszeile 1 P.

Typischer Fehler: teilnahme und bewertung gleichzeitig per LEFT JOIN verbinden und dann gruppieren. Jede der 4 Teilnahmen wird dann mit jeder der 2 Bewertungen kombiniert: COUNT liefert 8 statt 4 Teilnehmende (der Durchschnitt bleibt gleich, weil jede Bewertung gleich oft vervielfacht wird). Abhilfe sind die Unterabfragen in der Spaltenliste – oder COUNT(DISTINCT t.benutzer_id) (dann aber nicht für SUM). Eine Lösung ohne Statusbedingung bei den Teilnahmen zählt auch die Teilnahme mit Status 'nicht_erschienen' mit (5 statt 4 Teilnehmende). Mit NOW() - INTERVAL 14 DAY statt CURDATE() wird nach 10:00 Uhr auch die Wanderung (Aktivität 2) erfasst; das ist vertretbar, wenn man „14 Tage“ auf die Uhrzeit genau versteht (Datumsbedingung trotzdem 1 P).

b) Erstelle die Anweisung, die anschließend die archivierten Aktivitäten löscht. Erläutere, was dabei in den Tabellen teilnahme, bewertung und meldung geschieht. (4 Punkte)

DELETE FROM aktivitaet
WHERE aktivitaet_id IN (SELECT aktivitaet_id FROM aktivitaet_archiv);
  • teilnahme und bewertung: Wegen ON DELETE CASCADE werden die fünf Teilnahmen und zwei Bewertungen von Aktivität 1 automatisch mitgelöscht.

  • meldung: Die Meldung bleibt erhalten (Nachvollziehbarkeit der Moderation), ihr Fremdschlüssel aktivitaet_id wird wegen ON DELETE SET NULL auf NULL gesetzt.

Bewertung: DELETE mit Unterabfrage auf das Archiv 2 P (dieselbe Bedingung wie in a) ist ebenfalls richtig, siehe aber c), 1 P), Erläuterung CASCADE 1 P, SET NULL 1 P.

c) Jonas fragt, warum Du in b) auf die Archivtabelle verweist, statt einfach die Bedingung aus a) zu wiederholen, und ob es schlimm wäre, wenn zwischen a) und b) ein Fehler auftritt. Begründe Dein Vorgehen und schreibe den Rahmen, mit dem a) und b) gemeinsam ausgeführt werden. (4 Punkte)

Verweis auf das Archiv: Gelöscht wird nur, was nachweislich archiviert wurde. Eine wiederholte Bedingung kann eine andere Menge treffen – etwa wenn zwischen den Anweisungen der Status einer Aktivität auf 'stattgefunden' gesetzt wird oder um Mitternacht CURDATE() wechselt. Dann würde eine Aktivität gelöscht, ohne archiviert zu sein. (1 P)

Fehler zwischen a) und b): Ohne Transaktion bliebe ein halber Zustand zurück (archiviert, aber nicht gelöscht – oder bei umgekehrter Reihenfolge gelöscht, aber nicht archiviert, was die Daten für immer vernichtet). Eine Transaktion macht beide Schritte zu einer Einheit: Entweder beide wirken oder keiner. (1 P)

START TRANSACTION;

INSERT INTO aktivitaet_archiv (…)
SELECT …;                      -- Anweisung aus a)

DELETE FROM aktivitaet
WHERE aktivitaet_id IN (SELECT aktivitaet_id FROM aktivitaet_archiv);

COMMIT;                        -- bei einem Fehler stattdessen ROLLBACK;

Bewertung: Rahmen mit START TRANSACTION (oder BEGIN) und COMMIT 1 P, ROLLBACK für den Fehlerfall 1 P. Das CREATE TABLE der Archivtabelle gehört nicht in die Transaktion – DDL-Anweisungen lösen in MariaDB einen impliziten COMMIT aus.

d) Setze Regel 2 um und gib an, welche Datensätze gelöscht werden. (3 Punkte)

DELETE FROM aktivitaet
WHERE status = 'abgesagt'
  AND beginn < CURDATE() - INTERVAL 14 DAY;

Gelöscht wird Aktivität 6 („Schach im Park“, 27.09.2026) und per CASCADE ihre eine Teilnahme.

Bewertung: Statusbedingung 1 P, Datumsbedingung 1 P, Ergebnis mit Hinweis auf die kaskadierte Teilnahme 1 P. DATEDIFF(CURDATE(), beginn) > 14 ist ebenso richtig.

e) Setze Regel 3 um. Bei der Anonymisierung sollen Vor- und Nachname durch „gelöscht“ ersetzt werden und die E-Mail-Adresse durch geloescht-<benutzer_id>@anonym.example (die Spalte ist UNIQUE). Straße, Hausnummer und Geschlecht werden geleert, der Status wird auf 'geloescht' gesetzt. Nutze die SQL-Kurzreferenz.

ea) Erstelle die SQL-Anweisung. Im Datenbestand ist nur Elena Wagner (benutzer_id 11, gesperrt, letzter Login am 01.03.2026) betroffen. (6 Punkte)

eb) Nimm Stellung, ob der Datensatz danach anonym ist. (2 Punkte)

ea)

UPDATE benutzer
SET vorname    = 'gelöscht',
    nachname   = 'gelöscht',
    email      = CONCAT('geloescht-', benutzer_id, '@anonym.example'),
    strasse    = NULL,
    hausnr     = NULL,
    geschlecht = NULL,
    status     = 'geloescht'
WHERE status = 'gesperrt'
  AND DATEDIFF(CURDATE(), letzter_login) > 180;

Ergebnis: Das Konto 11 hat jetzt vorname = nachname = 'gelöscht', email = 'geloescht-11@anonym.example', strasse, hausnr, geschlecht sind NULL, status = 'geloescht' (231 Tage seit dem letzten Login).

Bewertung: sieben Zuweisungen mit Komma 2 P (davon CONCAT mit der eigenen benutzer_id 1 P), NULL setzen mit = NULL im SET 1 P, Statusbedingung 1 P, Datumsbedingung 1 P, Status wird mitgeändert (sonst trifft die Regel das Konto beim nächsten Lauf erneut) 1 P. letzter_login < CURDATE() - INTERVAL 180 DAY ist ebenso richtig. Eine konstante E-Mail-Adresse für alle Konten scheitert an UNIQUE, sobald zwei Konten anonymisiert werden.

eb) Nicht vollständig: Geburtsdatum, Postleitzahl, Ort, Registrierungszeitpunkt und der Passwort-Hash bleiben stehen; zusammen mit der verbleibenden Meldung, in der Elena als gemeldete Person eingetragen ist (bei anderen Konten auch mit Teilnahmen oder Freundschaften), lässt sich die Person ggf. wieder bestimmen. Damit ist der Datensatz eher pseudonymisiert. Vorschlag: Geburtsdatum auf das Geburtsjahr vergröbern, Passwort-Hash überschreiben, nicht mehr benötigte verknüpfte Daten löschen – und prüfen, ob Meldungen aus Gründen der Nachvollziehbarkeit (berechtigtes Interesse) noch gebraucht werden. (2 P für Einschätzung mit Begründung)

f) Jonas wollte Konten ohne vollständige Adresse als „nicht verifiziert“ kennzeichnen und hat dazu geschrieben:

UPDATE benutzer SET verifiziert = FALSE;
WHERE strasse = NULL OR hausnr = NULL;

Beschreibe die Fehler und ihre Wirkung und korrigiere die Anweisung so, dass anonymisierte Konten nicht betroffen sind. (4 Punkte)

  • Das Semikolon nach FALSE beendet das UPDATE: Es setzt verifiziert = FALSE bei allen Konten. Die zweite Zeile ist eine eigene Anweisung und erzeugt einen Syntaxfehler – der Schaden ist aber schon passiert (ohne Transaktion nicht rückgängig zu machen). (1 P)

  • = NULL ist nie wahr; auch mit richtig gesetztem WHERE würde keine Zeile getroffen. Es muss IS NULL heißen. (1 P)

UPDATE benutzer
SET verifiziert = FALSE
WHERE (strasse IS NULL OR hausnr IS NULL)
  AND status <> 'geloescht';

Getroffen wird nur Jana Meier (8). Weil sie bereits nicht verifiziert ist, meldet MariaDB „Rows matched: 1 Changed: 0“. Ohne die Klammern bindet AND stärker als OR: Die Bedingung hieße strasse IS NULL OR (hausnr IS NULL AND status <> 'geloescht') und würde anonymisierte Konten (dort ist strasse NULL) doch erfassen – im Datenbestand Elena Wagner (11). (2 P für die Korrektur einschließlich Klammern)

g) In der Tabelle altdaten_import liegt noch ein Excel-Export des früheren Dienstleisters. Die Spalte name enthält Einträge der Form 'Haddad, Malik'. Bevor die Daten abgeglichen werden, sollen Nach- und Vorname in eigenen Spalten stehen.

ga) Erstelle die Anweisungen, mit denen Du die Spalten nachname und vorname (je VARCHAR(50)) ergänzt, die Inhalte aus name überträgst und die Spalte name anschließend entfernst. Nutze die SQL-Kurzreferenz. (5 Punkte)

gb) Im Altbestand ist bei Malik Haddad das Geburtsdatum als '1902-03-08' statt '2002-03-08' erfasst. Korrigiere den Eintrag so, dass auch bei einer zweiten Ausführung kein anderer Datensatz verändert wird. (2 Punkte)

ga)

ALTER TABLE altdaten_import
ADD COLUMN nachname VARCHAR(50),
ADD COLUMN vorname  VARCHAR(50);

UPDATE altdaten_import
SET nachname = TRIM(SUBSTRING_INDEX(name, ',', 1)),
    vorname  = TRIM(SUBSTRING_INDEX(name, ',', -1));

ALTER TABLE altdaten_import
DROP COLUMN name;

Ergebnis (Auszug):

zeile nachname vorname
1 Hoffmann Sabine
2 Haddad Malik
10 Çelik Deniz

Bewertung: ALTER TABLE … ADD 1 P, SUBSTRING_INDEX mit 1 und -1 2 P, TRIM für das Leerzeichen nach dem Komma 1 P, DROP COLUMN erst nach dem UPDATE 1 P. Vor dem DROP COLUMN lohnt eine Kontrolle mit SELECT name, nachname, vorname FROM altdaten_import; – das Entfernen der Spalte lässt sich nicht zurücknehmen.

gb)

UPDATE altdaten_import
SET geburtsdatum = '2002-03-08'
WHERE nachname = 'Haddad'
  AND vorname  = 'Malik'
  AND geburtsdatum = '1902-03-08';

Bewertung: Zuweisung im ISO-Format 1 P, Bedingung, die genau diesen Datensatz trifft (Primärschlüssel zeile = 2 ist ebenso richtig) 1 P. Durch die Bedingung auf den alten Wert ändert eine zweite Ausführung nichts mehr.

Aufgabe 2.4: Datenpflege in einer Mitfahr-App (Kernkompetenz)

(ca. 35 min Bearbeitungszeit)

Die Schülervertretung des Ratz und Rübe Berufskollegs (RRB) hat die Mitfahr-App „RatzFahrt“ angestoßen: Auszubildende aus dem Umland bilden für die Berufsschultage Fahrgemeinschaften. Du arbeitest im FIA-Projektteam und übernimmst Wartungsaufgaben, die Andrea Wolf (Datenschutzbeauftragte der Schule und Administratorin der App) festgelegt hat. Auszug aus dem Datenmodell (Primärschlüssel unterstrichen, Fremdschlüssel mit #):

  • nutzer (nutzer_id, pseudonym, vorname, nachname, email_schule, geburtsdatum, #klasse_id, rolle, einwilligung_eltern, fuehrerschein, registriert_am, aktiv)

  • haltepunkt (haltepunkt_id, name, ort, plz, breitengrad, laengengrad, entfernung_km)

  • fahrzeug (fahrzeug_id, #nutzer_id, typ, farbe, sitzplaetze)

  • angebot (angebot_id, #fahrer_id, #fahrzeug_id, #haltepunkt_id, richtung, datum, abfahrt, freie_plaetze, kostenbeitrag_eur, status, erstellt_am)

  • gesuch (gesuch_id, #nutzer_id, #haltepunkt_id, richtung, datum, zeit_von, zeit_bis, status, erstellt_am)

  • buchung (buchung_id, #angebot_id, #mitfahrer_id, #gesuch_id, status, angefragt_am, beantwortet_am)

  • bewertung (bewertung_id, #buchung_id, #von_nutzer_id, sterne, puenktlich, erstellt_am)

Auszug aus den Tabellendefinitionen:

CREATE TABLE fahrzeug (
  fahrzeug_id  INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  nutzer_id    INT UNSIGNED NOT NULL,
  typ          ENUM('PKW','Motorrad','Kleinbus') NOT NULL DEFAULT 'PKW',
  farbe        VARCHAR(20)  NULL,
  sitzplaetze  TINYINT UNSIGNED NOT NULL,        -- Mitfahrplätze ohne Fahrer*in
  CONSTRAINT chk_sitze CHECK (sitzplaetze BETWEEN 1 AND 8),
  FOREIGN KEY (nutzer_id) REFERENCES nutzer (nutzer_id) ON DELETE CASCADE
);
-- angebot:   status ENUM('offen','voll','abgesagt','durchgefuehrt'),
--            FOREIGN KEY (fahrer_id) und (fahrzeug_id) ohne ON DELETE-Regel
-- gesuch:    status ENUM('offen','vermittelt','zurueckgezogen','abgelaufen')
-- buchung:   status ENUM('angefragt','bestaetigt','abgelehnt','storniert'),
--            FOREIGN KEY (angebot_id) REFERENCES angebot (angebot_id) ON DELETE CASCADE,
--            FOREIGN KEY (gesuch_id)  REFERENCES gesuch (gesuch_id)   ON DELETE SET NULL
-- bewertung: FOREIGN KEY (buchung_id) REFERENCES buchung (buchung_id) ON DELETE CASCADE
Tabelle angebot (Auszug, mit Angaben aus haltepunkt)
angebot_id fahrer_id fahrzeug_id haltepunkt (Ort, km zur Schule) richtung datum freie_plaetze kostenbeitrag_eur status
1 1 1 1 (Gütersloh, 18,6) zur Schule 2026-10-19 2 1.80 offen
2 1 1 1 (Gütersloh, 18,6) nach Hause 2026-10-19 3 1.80 offen
3 6 3 3 (Halle, 15,2) zur Schule 2026-10-19 0 1.50 voll
4 7 4 2 (Herford, 17,9) zur Schule 2026-10-20 3 1.70 offen
5 9 5 4 (Bad Salzuflen, 19,4) zur Schule 2026-10-22 6 2.50 offen
6 5 2 1 (Gütersloh, 18,6) nach Hause 2026-10-15 3 0.00 durchgefuehrt
7 7 4 2 (Herford, 17,9) nach Hause 2026-10-14 4 1.70 abgesagt
Tabelle gesuch (Auszug)
gesuch_id nutzer_id datum status
1 2 2026-10-20 offen
2 3 2026-10-19 vermittelt
3 4 2026-10-19 vermittelt
4 8 2026-10-23 offen
5 4 2026-10-15 vermittelt
Tabelle buchung (zu Buchung 4 gibt es zwei Bewertungen)
buchung_id angebot_id mitfahrer_id gesuch_id status
1 3 3 2 bestaetigt
2 1 4 3 bestaetigt
3 1 8 NULL abgelehnt
4 6 4 5 bestaetigt
5 4 2 NULL angefragt

Minderjährige sind zurzeit Aylin Kaya (nutzer_id 2, geboren 02.06.2009, Einwilligung der Eltern vom 03.09.2026) und Lina Hartwig (8, geboren 23.11.2008, ohne Einwilligung). Ben Richter (1) ist am 17.01.2026 volljährig geworden.

a) Minderjährige dürfen die App nur mit Einwilligung der Erziehungsberechtigten nutzen. Deaktiviere alle Konten von Minderjährigen ohne Einwilligung. Nutze die SQL-Kurzreferenz. (4 Punkte)

UPDATE nutzer
SET aktiv = FALSE
WHERE TIMESTAMPDIFF(YEAR, geburtsdatum, CURDATE()) < 18
  AND einwilligung_eltern IS NULL;

Deaktiviert wird nur Lina Hartwig (8).

Bewertung: SET aktiv = FALSE 1 P, Altersberechnung mit TIMESTAMPDIFF (oder geburtsdatum > CURDATE() - INTERVAL 18 YEAR) 2 P, IS NULL 1 P. YEAR(CURDATE()) - YEAR(geburtsdatum) < 18 ist ungenau (berücksichtigt den Geburtstag nicht): 1 P für die Altersbedingung.

b) Offene Gesuche deaktivierter Konten sollen nicht mehr auf den Flurmonitoren erscheinen. Lösche sie und erläutere, warum Linas abgelehnte Buchungsanfrage dabei nicht verloren geht. (4 Punkte)

DELETE FROM gesuch
WHERE status = 'offen'
  AND nutzer_id IN (SELECT nutzer_id
                    FROM nutzer
                    WHERE aktiv = FALSE);

Gelöscht wird Gesuch 4. Linas Buchung 3 bezieht sich auf Angebot 1, nicht auf ein Gesuch (gesuch_id ist NULL); und selbst wenn sie auf das Gesuch verwiese, würde wegen ON DELETE SET NULL nur der Verweis geleert, die Buchung bliebe erhalten.

Bewertung: DELETE mit Unterabfrage 2 P, Statusbedingung 1 P, Erläuterung SET NULL bzw. fehlender Verweis 1 P.

c) Der Kostenbeitrag darf höchstens 0,10 € je Kilometer Entfernung des Haltepunkts zur Schule betragen. Setze bei allen offenen Angeboten, die diese Grenze überschreiten, den Kostenbeitrag auf den Höchstbetrag (auf Cent gerundet). Verwende einen Verbund im UPDATE (MariaDB-Syntax, siehe Kurzreferenz). Gib an, welches Angebot sich wie ändert. (6 Punkte)

UPDATE angebot AS a
  INNER JOIN haltepunkt AS h ON h.haltepunkt_id = a.haltepunkt_id
SET a.kostenbeitrag_eur = ROUND(h.entfernung_km * 0.10, 2)
WHERE a.status = 'offen'
  AND a.kostenbeitrag_eur > h.entfernung_km * 0.10;

Geändert wird nur Angebot 5 von Niklas Fischer (Bad Salzuflen, 19,4 km): von 2,50 € auf 1,94 €. Alle anderen offenen Angebote liegen unter ihrer Grenze (z. B. Gütersloh: 1,80 € ≤ 1,86 €).

Bewertung: UPDATE … INNER JOIN … ON 2 P, Zuweisung mit Berechnung und Rundung 1 P, Bedingung „überschreitet“ 1 P, Statusbedingung 1 P, Ergebnis 1 P.

Alternative ohne Verbund (Standard-SQL, korrelierte Unterabfragen):

UPDATE angebot
SET kostenbeitrag_eur = (SELECT ROUND(h.entfernung_km * 0.10, 2)
                         FROM haltepunkt AS h
                         WHERE h.haltepunkt_id = angebot.haltepunkt_id)
WHERE status = 'offen'
  AND kostenbeitrag_eur > (SELECT h.entfernung_km * 0.10
                           FROM haltepunkt AS h
                           WHERE h.haltepunkt_id = angebot.haltepunkt_id);

Wird abgerundet statt gerundet (FLOOR(h.entfernung_km * 10) / 100), ist das ebenso richtig – „höchstens“ ist damit sogar sicher eingehalten.

d) Am Abend des 20.10.2026 soll der Tagesabschluss laufen:

  1. Buchungsanfragen, die bis dahin unbeantwortet geblieben sind ('angefragt') und zu einem noch offenen Angebot bis einschließlich 20.10.2026 gehören, werden auf 'storniert' gesetzt.

  2. Offene und volle Angebote bis einschließlich 20.10.2026 gelten als durchgeführt.

  3. Offene Gesuche bis einschließlich 20.10.2026 sind abgelaufen.

Erstelle die drei SQL-Anweisungen und begründe, warum Schritt 1 vor Schritt 2 ausgeführt werden muss. (6 Punkte)

UPDATE buchung
SET status = 'storniert'
WHERE status = 'angefragt'
  AND angebot_id IN (SELECT angebot_id
                     FROM angebot
                     WHERE status = 'offen'
                       AND datum <= '2026-10-20');

UPDATE angebot
SET status = 'durchgefuehrt'
WHERE status IN ('offen', 'voll')
  AND datum <= '2026-10-20';

UPDATE gesuch
SET status = 'abgelaufen'
WHERE status = 'offen'
  AND datum <= '2026-10-20';

Ergebnis: Buchung 5 → 'storniert'; Angebote 1, 2, 3 und 4 → 'durchgefuehrt'; Gesuch 1 → 'abgelaufen'.

Reihenfolge: Schritt 1 erkennt die betroffenen Angebote am Status 'offen'. Nach Schritt 2 hätten sie diesen Status nicht mehr, die Unterabfrage fände kein Angebot und die Anfrage bliebe auf 'angefragt' stehen.

Bewertung: je Anweisung 1,5 P (Bedingungen mit IN bzw. <= und Datumsliteral im ISO-Format), Begründung 1,5 P. Mit CURDATE() statt '2026-10-20' ist es ebenfalls richtig, wenn der Abschluss am selben Tag läuft.

e) Neue Fahrzeuge werden in der Tabelle fahrzeug eingetragen.

ea) Marie Schulz (nutzer_id 7) hat einen neuen PKW mit 4 Mitfahrplätzen; die Farbe hat sie nicht angegeben. Erstelle die SQL-Anweisung mit möglichst kurzer Spaltenliste und gib den entstehenden Datensatz an. (2 Punkte)

eb) Niklas Fischer (9) möchte einen zweiten Kleinbus (grau) mit 9 Mitfahrplätzen eintragen. Erläutere, was bei der Anweisung INSERT INTO fahrzeug (nutzer_id, typ, farbe, sitzplaetze) VALUES (9, 'Kleinbus', 'grau', 9); geschieht. (2 Punkte)

ea)

INSERT INTO fahrzeug (nutzer_id, sitzplaetze)
VALUES (7, 4);
fahrzeug_id nutzer_id typ farbe sitzplaetze
6 7 PKW NULL 4

typ kommt aus dem DEFAULT, farbe bleibt NULL, die fahrzeug_id vergibt AUTO_INCREMENT. (1 P Anweisung, 1 P Datensatz)

eb) Die Bedingung chk_sitze erlaubt nur 1 bis 8 Mitfahrplätze. MariaDB lehnt das INSERT ab (Fehler 4025 „CONSTRAINT chk_sitze failed“), es wird kein Datensatz gespeichert. Ein Kleinbus mit Fahrer*in und 9 Mitfahrenden bräuchte ohnehin eine andere Führerscheinklasse – die Regel ist also auch fachlich sinnvoll. (2 P)

f) Zum Ende des Schuljahres müssen alle personenbezogenen Fahrtdaten gelöscht werden. Andrea möchte die Löschroutine vorab testen: Mit dem Stichtag 16.10.2026 sollen alle Angebote und Gesuche mit einem Datum vor dem Stichtag samt zugehörigen Buchungen und Bewertungen gelöscht werden. Weil es ein Test ist, soll am Ende alles wieder zurückgenommen werden.

fa) Erstelle die SQL-Anweisungen in einer Reihenfolge, die auch ohne ON DELETE CASCADE funktionieren würde, eingebettet in eine Transaktion, die am Ende zurückgenommen wird. Gib an, wie viele Datensätze je Tabelle gelöscht werden. (8 Punkte)

fb) Begründe die Reihenfolge und erläutere, welche Anweisungen wegen der vorhandenen ON DELETE-Regeln eigentlich entbehrlich wären. (3 Punkte)

fc) Andrea überlegt, für den echten Lauf am Schuljahresende TRUNCATE TABLE buchung; zu verwenden, „weil das schneller ist“. Nimm Stellung. (3 Punkte)

fa)

START TRANSACTION;

DELETE FROM bewertung
WHERE buchung_id IN (SELECT bu.buchung_id
                     FROM buchung AS bu
                       INNER JOIN angebot AS a ON a.angebot_id = bu.angebot_id
                     WHERE a.datum < '2026-10-16');

DELETE FROM buchung
WHERE angebot_id IN (SELECT angebot_id
                     FROM angebot
                     WHERE datum < '2026-10-16');

DELETE FROM angebot
WHERE datum < '2026-10-16';

DELETE FROM gesuch
WHERE datum < '2026-10-16';

ROLLBACK;
Gelöschte Datensätze im Testlauf
Tabelle gelöscht Datensätze
bewertung 2 beide Bewertungen zu Buchung 4
buchung 1 Buchung 4 (Angebot 6)
angebot 2 Angebote 6 und 7
gesuch 1 Gesuch 5

Nach ROLLBACK sind alle Datensätze wieder vorhanden.

Bewertung: Transaktion mit START TRANSACTION und ROLLBACK 2 P, DELETE für bewertung mit Unterabfrage über buchung und angebot 2 P, buchung 1 P, angebot 1 P, gesuch 1 P, Anzahlen 1 P.

fb) Die Datensätze werden von unten nach oben gelöscht: Bewertungen verweisen auf Buchungen, Buchungen auf Angebote. Ohne CASCADE würde das Löschen eines Angebots mit Buchungen am Fremdschlüssel scheitern (Fehler 1451). Das Gesuch kann vor oder nach den Buchungen gelöscht werden, weil buchung.gesuch_id mit SET NULL definiert ist (sonst müsste es nach den Buchungen kommen). Mit den vorhandenen Regeln wären die ersten beiden DELETE-Anweisungen entbehrlich: DELETE FROM angebot … löscht per CASCADE die Buchungen und darüber per CASCADE die Bewertungen. Die ausdrückliche Reihenfolge macht die Routine aber unabhängig von den Regeln und zeigt im Protokoll, was gelöscht wird. (1 P Reihenfolge, 1 P Gesuch/SET NULL, 1 P Kaskade)

fc) TRUNCATE ist hier ungeeignet:

  • Es löscht immer alle Zeilen, eine Bedingung (Stichtag) ist nicht möglich.

  • Es gehört zur DDL und beendet in MariaDB eine laufende Transaktion mit einem impliziten COMMIT; ein ROLLBACK ist danach nicht möglich.

  • Bei InnoDB wird TRUNCATE für eine Tabelle abgelehnt, auf die ein Fremdschlüssel einer anderen Tabelle verweist (hier bewertung.buchung_id).

Bei den kleinen Datenmengen einer Schul-App ist der Geschwindigkeitsvorteil zudem unerheblich. DELETE mit Bedingung in einer Transaktion ist die richtige Wahl. (je Argument 1 P)

Aufgabe 3.1: Händlerdaten ins neue Händlerportal übernehmen (Profilkompetenz)

(ca. 32 min Bearbeitungszeit)

Die CSB-Rad GmbH löst ihr altes Bestellprogramm für den Fachhandel durch ein Händlerportal ab. Natalia Wróbel (Buchhaltung und Controlling) hat die Händlerdaten aus dem alten Programm bereits bereinigt und in die Übergangstabelle haendler_bereinigt geladen. Die Spaltennamen stammen noch aus dem alten Export. Deine Aufgabe ist es, die Daten in die neuen Tabellen des Portals zu übernehmen.

CREATE TABLE haendler_bereinigt (
  `HNr`            INT UNSIGNED NOT NULL PRIMARY KEY,
  `Firma`          VARCHAR(100) NOT NULL,
  `Ansprechperson` VARCHAR(80)  NULL,
  `Strasse`        VARCHAR(100) NULL,
  `PLZ`            CHAR(5)      NOT NULL,
  `Ort`            VARCHAR(60)  NOT NULL,
  `E-Mail`         VARCHAR(100) NULL,
  `Rabatt`         DECIMAL(4,1) NULL,
  `Kunde seit`     DATE         NULL,
  `Status`         CHAR(1)      NOT NULL,   -- A aktiv, D Dublette, G Geschäft geschlossen, K Klärung offen
  `Bestellweg`     VARCHAR(10)  NOT NULL    -- 'Programm' = bisher über das alte Bestellprogramm
);
Inhalt der Übergangstabelle haendler_bereinigt (ohne die Spalten Ansprechperson, Strasse und E-Mail; sie sind außer bei 1026 überall gefüllt)
HNr Firma PLZ Ort Rabatt Kunde seit Status Bestellweg
1004 Radhaus Bielefeld-Süd GmbH 33649 Bielefeld 4.0 2024-11-05 A Programm
1011 Zweirad Lohmann e. K. 32756 Detmold 4.0 2019-03-01 A Telefon
1012 Radhaus Bielefeld Süd 33649 Bielefeld 5.0 NULL D Programm
1017 Velowerk Münster GmbH 48153 Münster 4.0 2020-07-12 A Programm
1023 Leipziger Radkontor GmbH 04275 Leipzig 4.0 2022-03-14 A E-Mail
1026 Fahrradladen am Sparrenberg 33602 Bielefeld NULL 2018-04-01 K Telefon
1031 Radsport Kemper GmbH 33378 Rheda-Wiedenbrück 4.0 2018-08-20 G Programm
1034 Pedale & Co. OHG 32105 Bad Salzuflen 4.0 NULL A Programm

Die neuen Tabellen des Portals:

CREATE TABLE bestellweg (
  bestellweg_id  INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  bezeichnung    VARCHAR(20)  NOT NULL UNIQUE
);

CREATE TABLE haendler (
  haendler_nr    INT UNSIGNED NOT NULL PRIMARY KEY,
  firma          VARCHAR(100) NOT NULL,
  ansprechperson VARCHAR(80)  NULL,
  strasse        VARCHAR(100) NOT NULL,
  plz            CHAR(5)      NOT NULL,
  ort            VARCHAR(60)  NOT NULL,
  email          VARCHAR(100) NOT NULL UNIQUE,
  rabatt_prozent DECIMAL(4,1) NOT NULL DEFAULT 0.0,
  kunde_seit     DATE         NULL,
  bestellweg_id  INT UNSIGNED NULL,
  portal_aktiv   BOOLEAN      NOT NULL DEFAULT FALSE,
  angelegt_am    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_h_bestellweg FOREIGN KEY (bestellweg_id) REFERENCES bestellweg (bestellweg_id)
);

a) Lege in der Tabelle bestellweg mit einer Anweisung die Bestellwege „Portal“, „Telefon“ und „E-Mail“ an. Die Schlüssel vergibt die Datenbank. (2 Punkte)

INSERT INTO bestellweg (bezeichnung)
VALUES ('Portal'), ('Telefon'), ('E-Mail');

Bewertung: Spaltenliste ohne Schlüsselspalte (die Werte vergibt AUTO_INCREMENT) 1 Punkt, drei Wertetupel in einer Anweisung mit Texten in Hochkommata 1 Punkt. Die Datenbank vergibt die Schlüssel 1, 2 und 3.

b) Erläutere, was referentielle Integrität bedeutet und warum bestellweg deshalb vor haendler befüllt werden muss. (3 Punkte)

Referentielle Integrität bedeutet, dass jeder Fremdschlüsselwert auf einen vorhandenen Primärschlüsselwert der referenzierten Tabelle verweist oder NULL ist (1 Punkt). Das DBMS prüft das bei jedem INSERT, UPDATE und DELETE (1 Punkt). haendler.bestellweg_id verweist auf bestellweg: Gäbe es dort noch keine Zeilen, würde jede Übernahme mit gesetztem Bestellweg abgelehnt – also werden Nachschlage- und Elterntabellen zuerst befüllt, abhängige Tabellen danach (1 Punkt).

c) Übernimm mit einer Anweisung alle aktiven Händler aus haendler_bereinigt in haendler. Wer bisher über das alte Programm bestellt hat, erhält den Bestellweg „Portal“; alle anderen behalten ihren Bestellweg. portal_aktiv und angelegt_am sollen ihre Standardwerte erhalten. Die Anweisung soll unabhängig davon funktionieren, welche Schlüssel die Bestellwege in a) bekommen haben. (9 Punkte)

INSERT INTO haendler (haendler_nr, firma, ansprechperson, strasse, plz, ort,
                      email, rabatt_prozent, kunde_seit, bestellweg_id)
SELECT h.`HNr`, h.`Firma`, h.`Ansprechperson`, h.`Strasse`, h.`PLZ`, h.`Ort`,
       h.`E-Mail`, h.`Rabatt`, h.`Kunde seit`, w.bestellweg_id
FROM haendler_bereinigt AS h
  LEFT JOIN bestellweg AS w
         ON w.bezeichnung = CASE h.`Bestellweg`
                              WHEN 'Programm' THEN 'Portal'
                              ELSE h.`Bestellweg`
                            END
WHERE h.`Status` = 'A';

Bewertung: Spaltenliste von haendler ohne portal_aktiv und angelegt_am, damit beide ihre Standardwerte erhalten (2 Punkte); SELECT mit denselben Spalten in derselben Reihenfolge – zugeordnet wird nur nach der Position, die Spaltennamen dürfen abweichen (2 Punkte); Spaltennamen mit Bindestrich bzw. Leerzeichen in Backticks (1 Punkt); Verbund mit bestellweg statt fester Schlüssel (2 Punkte); Umsetzen von „Programm“ auf „Portal“ mit CASE (1 Punkt); Bedingung Status = 'A' (1 Punkt).

Ergebnis: 5 Zeilen eingefügt.

Tabelle haendler nach der Übernahme (Auszug)
haendler_nr firma kunde_seit bestellweg_id portal_aktiv
1004 Radhaus Bielefeld-Süd GmbH 2024-11-05 1 0
1011 Zweirad Lohmann e. K. 2019-03-01 2 0
1017 Velowerk Münster GmbH 2020-07-12 1 0
1023 Leipziger Radkontor GmbH 2022-03-14 3 0
1034 Pedale & Co. OHG NULL 1 0

Der LEFT JOIN sorgt dafür, dass ein Händler auch dann übernommen wird, wenn sein Bestellweg nicht in bestellweg vorkommt (dann mit NULL); ein INNER JOIN ist hier ebenfalls richtig, weil alle Bestellwege vorhanden sind. Ebenfalls richtig ist eine Unterabfrage in der Spaltenliste: (SELECT w.bestellweg_id FROM bestellweg AS w WHERE w.bezeichnung = CASE … END). Die Zeilen 1012 (Dublette), 1026 (Klärung) und 1031 (geschlossen) werden bewusst nicht übernommen. Zur Kontrolle vergleicht man die Zeilenzahl der Quelle (SELECT COUNT(*) FROM haendler_bereinigt WHERE Status = 'A'; liefert 5) mit den eingefügten Zeilen.

d) Jonas, Dein Mit-Azubi, schlägt für c) eine kürzere Anweisung vor, „weil die Spalten doch inhaltlich zusammenpassen“:

INSERT INTO haendler
SELECT * FROM haendler_bereinigt WHERE Status = 'A';

Beurteile den Vorschlag. (3 Punkte)

Der Vorschlag scheitert: Ohne Spaltenliste erwartet haendler Werte für alle 12 Spalten, SELECT * liefert aber nur 11 (MariaDB: „Column count doesn’t match value count“) (1 Punkt). Selbst bei gleicher Spaltenzahl ordnet MariaDB nur nach der Position zu, nicht nach Namen oder Inhalt: Status ('A') würde in bestellweg_id landen und Bestellweg ('Programm') in portal_aktiv – das scheitert oder speichert Unsinn (1 Punkt). Außerdem fehlt die Umsetzung „Programm“ → „Portal“ über die Tabelle bestellweg. SELECT * eignet sich nur, wenn Quelle und Ziel exakt gleich aufgebaut sind (z. B. eine mit CREATE TABLE … LIKE angelegte Archivtabelle) (1 Punkt).

e) Die erste Pilotgruppe des Portals bilden alle Händler mit dem Bestellweg „Portal“, die seit mindestens einem Jahr Kunde sind. Schalte das Portal für sie frei (portal_aktiv), ohne die Schlüssel der Bestellwege oder das heutige Datum fest in die Anweisung zu schreiben. Gib an, welche Händler danach freigeschaltet sind, und begründe, warum Pedale & Co. nicht dazugehört. (6 Punkte)

UPDATE haendler
SET portal_aktiv = TRUE
WHERE bestellweg_id = (SELECT bestellweg_id
                       FROM bestellweg
                       WHERE bezeichnung = 'Portal')
  AND kunde_seit <= CURDATE() - INTERVAL 1 YEAR;

Bewertung: UPDATE … SET portal_aktiv = TRUE (1 Punkt); Bestellweg über eine Unterabfrage auf die Bezeichnung (2 Punkte); Datumsbedingung relativ zum heutigen Datum (1 Punkt). Freigeschaltet sind 1004 (Radhaus Bielefeld-Süd, Kunde seit 05.11.2024) und 1017 (Velowerk Münster) (1 Punkt). Pedale & Co. hat in kunde_seit den Wert NULL; jeder Vergleich mit NULL ergibt unbekannt, und WHERE übernimmt nur Zeilen, bei denen die Bedingung wahr ist (1 Punkt). Zweirad Lohmann und Leipziger Radkontor bestellen nicht über das Portal.

f) Beim Velowerk Münster ist ab sofort Elif Demir Ansprechperson, erreichbar unter elif.demir@velowerk-ms.example. Ändere den Datensatz und begründe, welche Bedingung Du dafür wählst. (3 Punkte)

UPDATE haendler
SET ansprechperson = 'Elif Demir',
    email          = 'elif.demir@velowerk-ms.example'
WHERE haendler_nr = 1017;

Bewertung: zwei Zuweisungen mit Komma getrennt (1 Punkt), Bedingung auf den Primärschlüssel (1 Punkt), Begründung (1 Punkt): Nur der Primärschlüssel identifiziert genau einen Datensatz. Firmennamen sind nicht als eindeutig definiert und werden unterschiedlich geschrieben (siehe die Dublette „Radhaus Bielefeld Süd“); eine Bedingung wie WHERE firma = 'Velowerk Münster GmbH' träfe womöglich mehrere oder gar keine Zeilen.

g) Jonas hat für einen neuen Händler folgende Anweisung vorbereitet, die MariaDB ablehnt:

INSERT INTO haendler (haendler_nr, firma, ansprechperson, strasse, plz, ort, e-mail,
                      rabatt_prozent, kunde_seit)
VALUES (1040, Fahrradwerkstatt Kessebohm, 'Anke Kessebohm', 'Herforder Straße 120',
        33609, 'Bielefeld', 'werkstatt@kessebohm.example', 3,5, 01.11.2026);

Nenne vier Fehler und gib die korrigierte Anweisung an. (6 Punkte)

Fehler (je 1 Punkt, höchstens 4):

  • Die Spalte heißt email; e-mail liest MariaDB als Rechnung e minus mail – in einer Spaltenliste ein Syntaxfehler.

  • Der Firmenname ist ein Text und muss in Hochkommata stehen.

  • 3,5 mit Dezimalkomma sind zwei Werte (3 und 5); damit stimmt die Zahl der Werte nicht mehr mit der Spaltenliste überein. Richtig ist 3.5.

  • Das Datum muss als Zeichenkette im ISO-Format stehen: '2026-11-01'.

  • Die Postleitzahl ist als CHAR(5) definiert und gehört in Hochkommata; als Zahl verlöre z. B. 04275 die führende Null.

INSERT INTO haendler (haendler_nr, firma, ansprechperson, strasse, plz, ort, email,
                      rabatt_prozent, kunde_seit)
VALUES (1040, 'Fahrradwerkstatt Kessebohm', 'Anke Kessebohm', 'Herforder Straße 120',
        '33609', 'Bielefeld', 'werkstatt@kessebohm.example', 3.5, '2026-11-01');

Korrigierte Anweisung: 2 Punkte. bestellweg_id bleibt NULL, portal_aktiv erhält FALSE, angelegt_am den aktuellen Zeitpunkt.

h) Nach erfolgreicher Übernahme sollen aus haendler_bereinigt alle Zeilen gelöscht werden, die bereits in haendler stehen; die übrigen bleiben für die Klärung erhalten. Formuliere die Anweisung und gib an, welche Zeilen übrig bleiben. (3 Punkte)

DELETE FROM haendler_bereinigt
WHERE `HNr` IN (SELECT haendler_nr
                FROM haendler);

Bewertung: DELETE FROM mit Bedingung (1 Punkt), Unterabfrage auf die übernommenen Schlüssel (1 Punkt), Ergebnis (1 Punkt): 5 Zeilen gelöscht, übrig bleiben 1012 (Dublette), 1026 (Klärung offen) und 1031 (geschlossen). TRUNCATE wäre falsch, weil es alle Zeilen entfernt. Eine Bedingung WHERE Status = 'A' führt hier zum selben Ergebnis, prüft aber nicht, ob die Übernahme wirklich geklappt hat.

Aufgabe 3.2: Termine, Prüfprotokolle und Stammdaten in der Werkstatt (Profilkompetenz)

(ca. 31 min Bearbeitungszeit)

In der Werkstatt der CSB-Rad GmbH in Bielefeld-Sennestadt plant Bastian Ludwig (Werkstattleitung) Montagen und Prüfungen in festen Zeitfenstern (Slot 1 = 8 Uhr, 2 = 10 Uhr, 3 = 13 Uhr, 4 = 15 Uhr). Du betreust als Auszubildende*r die Werkstattdatenbank. Ausschnitt aus dem Datenbankschema:

CREATE TABLE anlage (
  anlage_id   INT UNSIGNED NOT NULL PRIMARY KEY,
  bezeichnung VARCHAR(40)  NOT NULL UNIQUE
);

CREATE TABLE montagetermin (
  termin_id   INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  anlage_id   INT UNSIGNED NOT NULL,
  datum       DATE         NOT NULL,
  slot        TINYINT UNSIGNED NOT NULL,
  auftragsnr  CHAR(11)     NOT NULL,
  CONSTRAINT uq_belegung UNIQUE (anlage_id, datum, slot),
  CONSTRAINT fk_mt_anlage FOREIGN KEY (anlage_id) REFERENCES anlage (anlage_id)
);

CREATE TABLE pruefprotokoll (
  protokoll_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  zeitpunkt    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  anlage_id    INT UNSIGNED NOT NULL,
  auftragsnr   CHAR(11)     NOT NULL,
  akku_temp_c  DECIMAL(3,1) NOT NULL,
  bestanden    BOOLEAN      NOT NULL,
  CONSTRAINT fk_pp_anlage FOREIGN KEY (anlage_id) REFERENCES anlage (anlage_id)
);

CREATE TABLE leistungsart (
  leistungsart_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  bezeichnung     VARCHAR(60)  NOT NULL UNIQUE,
  stundensatz     DECIMAL(5,2) NOT NULL,
  aktiv           BOOLEAN      NOT NULL DEFAULT TRUE,
  CONSTRAINT chk_satz CHECK (stundensatz > 0)
);

CREATE TABLE werkstattauftrag (
  auftragsnr  CHAR(11)     NOT NULL PRIMARY KEY,
  kunde_id    INT UNSIGNED NOT NULL,
  annahme     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  zahlstatus  VARCHAR(10)  NOT NULL DEFAULT 'offen'
);

CREATE TABLE arbeitsposition (
  auftragsnr      CHAR(11)     NOT NULL,
  pos_nr          TINYINT UNSIGNED NOT NULL,
  leistungsart_id INT UNSIGNED NOT NULL,
  stunden         DECIMAL(4,2) NOT NULL,
  PRIMARY KEY (auftragsnr, pos_nr),
  CONSTRAINT fk_ap_auftrag  FOREIGN KEY (auftragsnr)      REFERENCES werkstattauftrag (auftragsnr) ON DELETE CASCADE,
  CONSTRAINT fk_ap_leistung FOREIGN KEY (leistungsart_id) REFERENCES leistungsart (leistungsart_id)
);
Tabelle anlage
anlage_id bezeichnung
1 Montageplatz 1
2 Montageplatz 2
3 Prüfstand E-Antrieb
Tabelle montagetermin
termin_id anlage_id datum slot auftragsnr
1 3 2026-11-05 1 W-2026-0151
2 3 2026-11-05 2 W-2026-0152
3 1 2026-11-05 2 W-2026-0153
4 3 2026-11-06 1 W-2026-0154
5 3 2026-11-05 4 W-2026-0155
6 3 2026-11-12 2 W-2026-0160
Tabelle leistungsart
leistungsart_id bezeichnung stundensatz aktiv
1 Inspektion Basis 58.00 1
2 Bremsen entlüften 68.00 1
3 Nabenschaltung einstellen 56.00 1
4 Akku-Diagnose 78.00 1
Tabelle werkstattauftrag (Auszug)
auftragsnr zahlstatus
W-2026-0141 1
W-2026-0142 bezahlt
W-2026-0147 0
W-2026-0148 1
W-2026-0150 offen
W-2026-0151 0
Tabelle arbeitsposition
auftragsnr pos_nr leistungsart_id stunden
W-2026-0141 1 1 1.00
W-2026-0141 2 3 0.50
W-2026-0147 1 2 0.75
W-2026-0151 1 4 1.25

a) Der Prüfstand für E-Antriebe fällt am 05.11.2026 aus und wird an diesem Tag repariert. Ermittle mit einer Abfrage alle Termine, die an diesem Tag auf dem Prüfstand geplant sind (Datum, Slot und Auftragsnummer, nach Slot sortiert). Die Anlage soll über ihre Bezeichnung ausgewählt werden. (3 Punkte)

SELECT t.datum, t.slot, t.auftragsnr
FROM montagetermin AS t
  INNER JOIN anlage AS a ON a.anlage_id = t.anlage_id
WHERE a.bezeichnung = 'Prüfstand E-Antrieb'
  AND t.datum = '2026-11-05'
ORDER BY t.slot;
Ergebnis der Prüfabfrage
datum slot auftragsnr
2026-11-05 1 W-2026-0151
2026-11-05 2 W-2026-0152
2026-11-05 4 W-2026-0155

Bewertung: Verbund mit anlage (1 Punkt), beide Bedingungen mit ISO-Datum (1 Punkt), Spalten und Sortierung (1 Punkt). Diese Abfrage ist zugleich die Kontrolle vor dem UPDATE in b): Sie zeigt, welche Zeilen sich ändern werden.

b) Verschiebe diese Termine um genau eine Woche. Die Anlage soll auch hier über ihre Bezeichnung ausgewählt werden. (5 Punkte)

UPDATE montagetermin
SET datum = datum + INTERVAL 7 DAY   -- oder DATE_ADD(datum, INTERVAL 7 DAY)
WHERE datum = '2026-11-05'
  AND anlage_id = (SELECT anlage_id
                   FROM anlage
                   WHERE bezeichnung = 'Prüfstand E-Antrieb');

Bewertung: UPDATE montagetermin SET datum = … (1 Punkt), Datumsrechnung um sieben Tage mit einer Datumsfunktion bzw. INTERVAL (2 Punkte), Bedingung auf das Datum (1 Punkt), Anlage über eine Unterabfrage auf die Bezeichnung (1 Punkt). Statt der Unterabfrage ist in MariaDB auch UPDATE montagetermin AS t INNER JOIN anlage AS a ON … SET t.datum = … richtig.

c) MariaDB bricht Deine Anweisung aus b) mit folgender Meldung ab: Duplicate entry '3-2026-11-12-2' for key 'uq_belegung'. Erläutere die Ursache, gib an, wie viele Termine nach dem Abbruch verschoben sind, und beschreibe einen Lösungsweg. (4 Punkte)

Ursache (1 Punkt): Die Kombination aus Anlage, Datum und Slot ist eindeutig (uq_belegung). Am 12.11.2026 ist Slot 2 des Prüfstands schon mit Auftrag W-2026-0160 belegt; der verschobene Termin W-2026-0152 würde denselben Slot belegen.

Wirkung (1 Punkt): Bei InnoDB-Tabellen wird eine einzelne Anweisung ganz oder gar nicht ausgeführt. Auch der bereits verschobene Termin aus Slot 1 wird zurückgenommen – es sind 0 Termine verschoben.

Lösungsweg (2 Punkte): Zuerst den Konflikt auflösen, dann verschieben, z. B. den Termin W-2026-0160 auf den freien Slot 3 legen (nach Rücksprache mit Bastian Ludwig) und danach das UPDATE aus b) erneut ausführen. Beide Anweisungen gehören in eine Transaktion, damit nie ein halber Stand entsteht:

START TRANSACTION;

UPDATE montagetermin
SET slot = 3
WHERE auftragsnr = 'W-2026-0160';

UPDATE montagetermin
SET datum = datum + INTERVAL 7 DAY
WHERE datum = '2026-11-05'
  AND anlage_id = (SELECT anlage_id
                   FROM anlage
                   WHERE bezeichnung = 'Prüfstand E-Antrieb');

COMMIT;

Danach liegen am 12.11.2026 auf dem Prüfstand die Slots 1 (W-2026-0151), 2 (W-2026-0152), 3 (W-2026-0160) und 4 (W-2026-0155). Andere sinnvolle Wege (betroffenen Termin auf einen anderen Tag legen, Konflikt vorher mit einer Abfrage suchen) sind ebenfalls richtig.

d) Jonas hätte in b) einfach SET datum = datum + 7 geschrieben und berichtet, das habe im Test mit dem 05.11.2026 funktioniert. Erläutere, was MariaDB dabei rechnet, und zeige an einem Beispiel, wann es schiefgeht. (3 Punkte)

MariaDB rechnet bei datum + 7 nicht mit Tagen, sondern wandelt das Datum in die Zahl 20261105 um und addiert 7 (1 Punkt). Das Ergebnis 20261112 lässt sich zufällig wieder als gültiges Datum 2026-11-12 lesen (1 Punkt). Geht die Rechnung über ein Monatsende, entsteht kein gültiges Datum: Aus dem 28.11.2026 würde 20261135. MariaDB lehnt das im Strict Mode mit „Incorrect date value“ ab, ohne Strict Mode wird 0000-00-00 gespeichert (1 Punkt). Richtig ist datum + INTERVAL 7 DAY oder DATE_ADD(datum, INTERVAL 7 DAY). In Oracle und PostgreSQL bedeutet datum + 7 dagegen tatsächlich „sieben Tage später“ – ein Grund, die Schreibweise des eingesetzten DBMS zu kennen.

e) Der Prüfstand schreibt jede Messung in die Tabelle pruefprotokoll. Füge die Messung für Auftrag W-2026-0154 ein: Akkutemperatur 31,4 °C, Prüfung nicht bestanden. Den Zeitpunkt soll die Datenbank selbst setzen. (3 Punkte)

INSERT INTO pruefprotokoll (anlage_id, auftragsnr, akku_temp_c, bestanden)
VALUES (3, 'W-2026-0154', 31.4, FALSE);

Bewertung: Spaltenliste ohne protokoll_id und zeitpunkt – beide setzt die Datenbank (AUTO_INCREMENT, DEFAULT CURRENT_TIMESTAMP) (1 Punkt); Dezimalpunkt statt Komma (1 Punkt); Wahrheitswert FALSE bzw. 0 ohne Hochkommata und Auftragsnummer als Text (1 Punkt). Ebenfalls richtig ist die Anlage über eine Unterabfrage ((SELECT anlage_id FROM anlage WHERE bezeichnung = 'Prüfstand E-Antrieb')).

f) Alle Stundensätze steigen zum 01.01.2027 um 2,5 %. Formuliere die Anweisung (Ergebnis auf Cent gerundet) und gib den neuen Stundensatz für „Akku-Diagnose“ an. (3 Punkte)

UPDATE leistungsart
SET stundensatz = ROUND(stundensatz * 1.025, 2);

Bewertung: Faktor 1.025 (1 Punkt), ROUND(…, 2) (1 Punkt), neuer Satz Akku-Diagnose 78,00 € · 1,025 = 79,95 € (1 Punkt). Ein WHERE ist hier nicht nötig, weil ausdrücklich alle Zeilen geändert werden sollen; die übrigen Sätze lauten danach 59,45 €, 69,70 € und 57,40 €.

g) Durch einen Fehler im alten Kassenprogramm steht in zahlstatus bei einigen Aufträgen '1' (bezahlt) oder '0' (offen) statt des Texts. Bereinige beide Fälle mit einer Anweisung, ohne andere Zeilen zu verändern. (4 Punkte)

UPDATE werkstattauftrag
SET zahlstatus = CASE zahlstatus
                   WHEN '1' THEN 'bezahlt'
                   WHEN '0' THEN 'offen'
                 END
WHERE zahlstatus IN ('0', '1');

Bewertung: CASE mit beiden Fällen (2 Punkte), Bedingung, die nur die fehlerhaften Zeilen auswählt (1 Punkt), Werte als Text in Hochkommata (1 Punkt). 4 Zeilen werden geändert (0141 und 0148 auf „bezahlt“, 0147 und 0151 auf „offen“). Ohne WHERE würde CASE bei den übrigen Zeilen keinen Zweig finden und NULL liefern – das scheitert an NOT NULL bzw. löscht den Status. Ebenfalls richtig ist ELSE zahlstatus statt der Bedingung. Zwei einzelne UPDATE-Anweisungen lösen das Problem auch, erfüllen aber nicht die Vorgabe „eine Anweisung“ (1 Punkt Abzug).

h) Jonas schlägt für g) vor: UPDATE werkstattauftrag SET zahlstatus = 'bezahlt' WHERE bezahlt = 1; Beurteile den Vorschlag. (2 Punkte)

Die Bedingung prüft eine Spalte bezahlt, die es nicht gibt – MariaDB meldet „Unknown column ‘bezahlt’ in ‘where clause’“ (1 Punkt). Gemeint ist die fehlerhafte Spalte selbst: WHERE zahlstatus = '1'. Außerdem bleibt der Fall '0' → „offen“ unbearbeitet (1 Punkt).

i) Die Leistungsart „Nabenschaltung einstellen“ wird nicht mehr angeboten. Jonas will sie mit folgender Anweisung entfernen:

DELETE FROM leistungsart
WHERE bezeichnung = 'Nabenschaltung einstellen';

ia) Erläutere, was beim Ausführen passiert, und begründe es mit der Tabellendefinition. (3 Punkte)

Das DELETE wird abgelehnt (MariaDB-Fehler 1451 „Cannot delete or update a parent row: a foreign key constraint fails“) (1 Punkt). Die Arbeitsposition 2 des Auftrags W-2026-0141 verweist über fk_ap_leistung auf die Leistungsart 3 (1 Punkt). Für diesen Fremdschlüssel ist keine ON DELETE-Regel angegeben, also gilt RESTRICT: Ein Elterndatensatz mit abhängigen Datensätzen darf nicht gelöscht werden, sonst ginge die referentielle Integrität verloren (1 Punkt).

ib) Jemand schlägt vor, den Fremdschlüssel fk_ap_leistung auf ON DELETE CASCADE (Löschweitergabe) umzustellen. Beurteile den Vorschlag und gib eine bessere Lösung als SQL-Anweisung an. (4 Punkte)

Mit Löschweitergabe würden beim Löschen der Leistungsart auch alle Arbeitspositionen gelöscht, die sie verwenden – hier die Position des abgeschlossenen Auftrags W-2026-0141. Damit fehlen abgerechnete Leistungen in Aufträgen und Rechnungen, die aufbewahrt werden müssen (2 Punkte). CASCADE passt nur für Teile, die ohne ihr Ganzes keinen Sinn haben (wie fk_ap_auftrag: Positionen eines gelöschten Auftrags). Besser ist es, die Leistungsart nur zu deaktivieren (2 Punkte):

UPDATE leistungsart
SET aktiv = FALSE
WHERE leistungsart_id = 3;

Die Werkstattsoftware bietet dann nur noch aktive Leistungsarten an; alte Aufträge bleiben vollständig. Die Bedingung über die eindeutige Bezeichnung ist ebenfalls richtig.

Anlage: SQL-Kurzreferenz DML (MariaDB)

Auszug der in diesen Aufgaben nützlichen Sprachelemente. Eckige Klammern kennzeichnen optionale Teile.

SQL-Kurzreferenz DML für MariaDB
Syntax Bedeutung
INSERT INTO tabelle (spalte1, spalte2, …) VALUES (wert1, wert2, …) [, (…)] fügt einen oder mehrere Datensätze ein; nicht genannte Spalten erhalten DEFAULT bzw. NULL
INSERT INTO tabelle (spalten) SELECT … fügt das Ergebnis einer Abfrage ein (Spaltenzahl, Reihenfolge und Datentypen müssen passen)
LAST_INSERT_ID() zuletzt in dieser Verbindung vergebener AUTO_INCREMENT-Wert
UPDATE tabelle SET spalte1 = ausdruck1 [, spalte2 = ausdruck2 …] [WHERE bedingung] ändert die Zeilen, die die Bedingung erfüllen (ohne WHERE: alle)
UPDATE t1 [AS a] INNER JOIN t2 [AS b] ON bedingung SET a.spalte = … [WHERE …] ändert Zeilen mit Bezug auf eine verbundene Tabelle (MariaDB)
DELETE FROM tabelle [WHERE bedingung] löscht die Zeilen, die die Bedingung erfüllen (ohne WHERE: alle)
CREATE TABLE neu LIKE vorhanden legt eine leere Tabelle mit gleicher Struktur an (MariaDB)
ALTER TABLE tabelle ADD COLUMN spalte typ [, ADD COLUMN …] ergänzt Spalten
ALTER TABLE tabelle MODIFY spalte typ [NOT NULL] ändert Datentyp bzw. Pflichtfeld-Eigenschaft einer Spalte
ALTER TABLE tabelle DROP COLUMN spalte entfernt eine Spalte
START TRANSACTION … COMMIT bzw. ROLLBACK Anweisungen als Einheit ausführen bzw. zurücknehmen
wert [NOT] IN (SELECT …) / [NOT] EXISTS (SELECT …) Bedingung mit Unterabfrage
ausdruck BETWEEN a AND b Bereich einschließlich der Grenzen
ausdruck IS [NOT] NULL Test auf NULL
CASE WHEN bedingung THEN wert [WHEN …] [ELSE wert] END Fallunterscheidung; Kurzform CASE ausdruck WHEN wert THEN …
ROUND(zahl, stellen) runden
CONCAT(text1, text2, …) Texte (und Zahlen) aneinanderhängen
SUBSTRING_INDEX(text, trennzeichen, n) Teil von text vor dem n-ten Trennzeichen; bei negativem n Teil nach dem n-ten Trennzeichen von rechts
TRIM(text) entfernt Leerzeichen am Anfang und Ende
CURDATE(), NOW() heutiges Datum, aktueller Zeitpunkt
datum - INTERVAL n DAY (auch MONTH, YEAR) Datum n Tage (Monate, Jahre) früher
datum + INTERVAL n DAY, DATE_ADD(datum, INTERVAL n DAY) Datum n Tage später
`Spaltenname mit Leerzeichen` Bezeichner mit Leer- oder Sonderzeichen in Backticks
DATEDIFF(ende, start) Differenz in Tagen
TIMESTAMPDIFF(YEAR, start, ende) (auch MONTH, DAY) Differenz in vollen Einheiten (z. B. Alter in Jahren)
DATE_FORMAT(datum, format) Datum als Text, z. B. '%Y-%m' → '2026-10', '%d.%m.%Y' → '18.10.2026'
YEAR(datum), MONTH(datum), DAY(datum) Teil eines Datums
COUNT(*), SUM(…), AVG(…), MIN(…), MAX(…) Aggregatfunktionen

Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zu INSERT, UPDATE und DELETE” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/sql-dml-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/db-sql/sql. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: