Zum Inhalt springen

11. Datenintegrität

Zu Zen-Modus wechseln

Datenintegrität - Sicherstellung der Datenqualität in SQL

Abschnitt betitelt „Datenintegrität - Sicherstellung der Datenqualität in SQL“

Motivation: Stellen Sie sich einen Online-Shop vor, in dem eine Kundenbestellung auf eine Kunden-ID verweist, die nicht existiert, oder ein Produktpreis versehentlich als negative Zahl gespeichert wird. Beide Situationen führen zu inkonsistenten, unzuverlässigen Daten - und letztlich zu falschen Entscheidungen oder defekten Anwendungen.

Datenintegrität ist die Gesamtheit der Regeln und Mechanismen, die genau diese Probleme verhindern. Sie stellt sicher, dass die in einer Datenbank gespeicherten Daten jederzeit korrekt, konsistent und sinnvoll sind.

Diese drei Begriffe - korrekt, konsistent und sinnvoll - lohnt es sich genau zu definieren, da sie im Alltag oft unpräzise verwendet werden.

  • Korrekt bedeutet, dass jeder gespeicherte Wert eine gültige Ausprägung dessen ist, was die Spalte darstellen soll. Eine Geburtsdatumsspalte enthält ein tatsächliches Kalenderdatum, keine beliebige Zeichenkette. Eine Preisspalte enthält eine nicht-negative Zahl, kein NULL und keinen Buchstaben. Korrektheit wird durch Datentypen, NOT NULL und CHECK-Constraints durchgesetzt.

  • Konsistent bedeutet, dass sich die Daten nicht selbst widersprechen - weder innerhalb einer einzelnen Zeile noch über verknüpfte Tabellen hinweg. Eine Bestellzeile, die auf einen nicht existierenden Kunden verweist, ist inkonsistent. Ein end_date, das früher liegt als das start_date in derselben Zeile, ist inkonsistent. Konsistenz wird durch FOREIGN KEY-Constraints und mehrspaltige CHECK-Constraints durchgesetzt.

  • Sinnvoll bedeutet, dass die Daten im realen Kontext, den sie abbilden sollen, einen Sinn ergeben. Ein Wert kann korrekt (technisch eine gültige Ganzzahl) und konsistent (keine Widersprüche) sein und trotzdem sinnlos - zum Beispiel eine Menge von null in einer Bestellzeile oder ein Rabatt von 150 %. Sinnvolle Daten erfüllen die Geschäftsregeln der Domäne. Diese Regeln werden durch benutzerdefinierte CHECK-Constraints durchgesetzt.

Ohne Integritätsregeln sind Datenbanken fragil:

  • Eine Fremdschlüsselspalte könnte auf eine Zeile verweisen, die gelöscht wurde.
  • Eine Gehaltsspalte könnte einen negativen Wert enthalten.
  • Zwei Benutzer könnten sich versehentlich dieselbe E-Mail-Adresse teilen.
  • Ein Pflichtfeld könnte leer gelassen werden.

Diese Probleme sind im Nachhinein oft schwer zu erkennen und teuer zu beheben. Indem man Integritäts-Constraints auf Datenbankebene definiert, werden Fehler sofort im Moment des Einfügens oder Aktualisierens abgefangen - unabhängig davon, welche Anwendung oder welcher Benutzer sie verursacht hat.

Die wesentlichen Vorteile der Durchsetzung von Datenintegrität sind:

VorteilErläuterung
ZuverlässigkeitDaten sind im gesamten System konsistent und vertrauenswürdig.
AutomatisierungDie Datenbank setzt Regeln automatisch durch und entlastet so den Anwendungscode.
Frühe FehlererkennungUngültige Daten werden sofort abgewiesen, bevor sie Schaden anrichten können.
InteroperabilitätMehrere Anwendungen, die dieselbe Datenbank nutzen, profitieren alle von denselben Regeln.

Datenintegrität wird traditionell in vier Kategorien unterteilt. Jede behandelt eine andere Dimension der Datenqualität.

Jede Tabelle muss in der Lage sein, jede ihrer Zeilen eindeutig zu identifizieren. Keine zwei Zeilen dürfen ununterscheidbar sein, und keine Zeile darf “anonym” sein.

Regel: Jede Tabelle muss einen Primärschlüssel haben. Der Primärschlüssel muss eindeutig sein und darf niemals NULL sein.

-- Die Spalte id identifiziert jeden Kunden eindeutig.
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);

Ohne Entitätsintegrität wäre es unmöglich, eine bestimmte Zeile zuverlässig anzusprechen - zum Beispiel beim Aktualisieren oder Löschen eines Datensatzes.

Wenn eine Tabelle auf Zeilen in einer anderen Tabelle verweist (über einen Fremdschlüssel), müssen diese referenzierten Zeilen tatsächlich existieren. Ein Verweis auf eine nicht existierende Zeile wird als hängender Verweis (dangling reference) bezeichnet und führt zu inkonsistenten Daten.

Regel: Jeder Wert in einer Fremdschlüsselspalte muss entweder mit einem vorhandenen Primärschlüsselwert in der referenzierten Tabelle übereinstimmen oder NULL sein (wenn die Beziehung optional ist).

-- Eine Bestellung muss auf einen existierenden Kunden verweisen.
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
placed_at DATE NOT NULL
);

Wenn jemand versucht, eine Bestellung mit einer customer_id einzufügen, die nicht in customers existiert, weist die Datenbank die Operation sofort zurück.

Jede Spalte hat eine definierte Domäne: die Menge aller Werte, die für diese Spalte gültig sind. Domänenintegrität stellt sicher, dass nur Werte innerhalb dieser Domäne gespeichert werden können.

Die Domäne einer Spalte wird bestimmt durch:

  • ihren Datentyp (z. B. INTEGER, DATE, VARCHAR(100))
  • zusätzliche Constraints (z. B. NOT NULL, CHECK, DEFAULT)

Beispiele:

  • Eine birth_date-Spalte vom Typ DATE weist den Text "hello" zurück.
  • Eine price-Spalte mit CHECK (price >= 0) weist -5.00 zurück.
  • Eine status-Spalte mit CHECK (status IN ('ACTIVE', 'INACTIVE')) weist jede andere Zeichenkette zurück.

Über die drei strukturellen Kategorien hinaus unterstützen Datenbanken auch benutzerdefinierte Regeln, die spezifische Geschäftsanforderungen widerspiegeln. Das sind Constraints, die nicht allein aus der Schemastruktur abgeleitet werden können, sondern explizit angegeben werden müssen.

Beispiele:

  • Ein discount-Prozentsatz muss zwischen 0 und 100 liegen.
  • Ein start_date darf nicht nach einem end_date liegen.
  • Eine quantity in einer Bestellzeile muss größer als null sein.

In SQL wird benutzerdefinierte Integrität in erster Linie mit CHECK-Constraints und in fortgeschritteneren Szenarien mit Triggern oder gespeicherten Prozeduren ausgedrückt.

SQL stellt eine Reihe von Constraints bereit, die direkt im Tabellenschema definiert werden. Die Datenbank-Engine wertet sie bei jedem INSERT, UPDATE und DELETE automatisch aus.

Eine als NOT NULL deklarierte Spalte muss immer einen Wert enthalten. Ein Versuch, eine Zeile mit NULL in dieser Spalte einzufügen oder zu aktualisieren, wird zurückgewiesen.

CREATE TABLE employees (
id SERIAL PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT -- optional (NULL ist erlaubt)
);

Verwenden Sie NOT NULL für jede Spalte, bei der ein leerer Wert keinen Sinn ergibt - zum Beispiel den Namen einer Person oder einen Produktcode.

Eine als UNIQUE deklarierte Spalte (oder Spaltenkombination) darf über alle Zeilen hinweg keine doppelten Werte enthalten. Anders als ein Primärschlüssel darf eine UNIQUE-Spalte NULL enthalten - und in den meisten Datenbanksystemen sind mehrere NULL-Werte erlaubt, weil NULL niemals als gleich zu irgendetwas gilt, auch nicht zu einem anderen NULL.

CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE
);

Das PRIMARY KEY-Constraint ist die zentrale Säule der Entitätsintegrität. Es vereint zwei Regeln in einer:

  1. Die Spalte (oder Spalten) muss eindeutig sein.
  2. Die Spalte (oder Spalten) darf niemals NULL sein.

Eine Tabelle kann nur einen Primärschlüssel haben, aber dieser Schlüssel kann mehrere Spalten umfassen (ein zusammengesetzter Primärschlüssel).

-- Einspaltiger Primärschlüssel
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
-- Zusammengesetzter Primärschlüssel (häufig in Zwischentabellen)
CREATE TABLE course_registrations (
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
PRIMARY KEY (student_id, course_id)
);

Ein FOREIGN KEY-Constraint verbindet eine Spalte in einer Tabelle mit dem Primärschlüssel einer anderen Tabelle und setzt damit referenzielle Integrität durch.

CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
total NUMERIC(10, 2) NOT NULL CHECK (total >= 0)
);

Was passiert, wenn sich die referenzierte Zeile ändert?

Abschnitt betitelt „Was passiert, wenn sich die referenzierte Zeile ändert?“

Sofort stellt sich eine Frage: Was soll mit den Kindzeilen geschehen, wenn eine referenzierte Zeile in der Elterntabelle gelöscht oder aktualisiert wird?

SQL erlaubt es Ihnen, dieses Verhalten über referenzielle Aktionen festzulegen:

AktionWirkung
RESTRICTDie Operation auf der Elternzeile wird abgewiesen, wenn Kindzeilen existieren. Das ist der Standard in den meisten Systemen.
CASCADEDie Operation wird automatisch auf alle Kindzeilen weitergegeben (z. B. werden sie gelöscht oder aktualisiert).
SET NULLDie Fremdschlüsselspalten in den Kindzeilen werden auf NULL gesetzt.
SET DEFAULTDie Fremdschlüsselspalten in den Kindzeilen werden auf ihren Standardwert gesetzt.
NO ACTIONÄhnlich wie RESTRICT, aber die Prüfung kann bis zum Ende einer Transaktion aufgeschoben werden.
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL
REFERENCES customers(id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
placed_at DATE NOT NULL
);

Das obige Beispiel gelesen:

  • ON DELETE RESTRICT - wenn jemand versucht, einen Kunden zu löschen, der noch Bestellungen hat, verweigert die Datenbank das Löschen.
  • ON UPDATE CASCADE - wenn sich die id eines Kunden ändert (selten, aber möglich), werden alle passenden customer_id-Werte in orders automatisch aktualisiert.

Ein CHECK-Constraint definiert eine logische Bedingung, die jede Zeile erfüllen muss. Die Bedingung kann jede Spalte derselben Tabelle und jeden SQL-Ausdruck einbeziehen, der ein boolesches Ergebnis liefert.

CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
discount NUMERIC(5, 2) CHECK (discount BETWEEN 0 AND 100)
);

Sie können auch CHECK-Constraints auf Tabellenebene schreiben, die mehrere Spalten miteinander vergleichen:

CREATE TABLE projects (
id SERIAL PRIMARY KEY,
start_date DATE NOT NULL,
end_date DATE,
CONSTRAINT chk_dates CHECK (end_date IS NULL OR end_date >= start_date)
);

Hier darf end_date leer gelassen werden (das Projekt läuft noch), aber wenn es gesetzt ist, darf es nicht früher als start_date liegen.

Ein DEFAULT-Wert ist im strengen Sinn kein Constraint - er weist keine ungültigen Daten zurück. Stattdessen liefert er einen Ersatzwert (Fallback), der automatisch verwendet wird, wenn bei einem INSERT kein expliziter Wert für eine Spalte angegeben wird.

CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Wenn Sie einen neuen Artikel einfügen, ohne published oder created_at anzugeben, füllt die Datenbank automatisch FALSE und den aktuellen Zeitstempel ein.

DEFAULT ist besonders nützlich für:

  • Zeitstempel - festhalten, wann eine Zeile erstellt wurde.
  • Boolesche Flags - einen sinnvollen Anfangszustand setzen (z. B. “inaktiv”, “unveröffentlicht”).
  • Statusspalten - jeden neuen Datensatz in einem definierten Zustand starten.

Die Datenbank generiert automatisch einen Namen für jedes Constraint, das keinen expliziten Namen hat. Diese automatisch generierten Namen sind oft kryptisch und schwer zu handhaben, wenn Sie eine Fehlermeldung erhalten oder ein Constraint später löschen müssen.

Es ist gute Praxis, jedes Constraint mit dem Schlüsselwort CONSTRAINT explizit zu benennen:

CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
unit_price NUMERIC(10, 2) NOT NULL,
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT,
CONSTRAINT chk_order_items_quantity
CHECK (quantity > 0),
CONSTRAINT chk_order_items_unit_price
CHECK (unit_price >= 0)
);

Eine weit verbreitete Namenskonvention ist:

  • pk_<table> - Primärschlüssel
  • uq_<table>_<column> - Unique-Constraint
  • fk_<table>_<referenced_table> - Fremdschlüssel
  • chk_<table>_<column> - Check-Constraint

Wenn eine Operation ein Constraint verletzt, bricht die Datenbank die Anweisung ab und gibt einen Fehler zurück. Die Daten werden nicht geändert. Hier sind die häufigsten Verletzungen und was sie auslöst:

ConstraintBeispiel einer Verletzung
NOT NULLEinfügen einer Zeile ohne Wert für eine erforderliche Spalte.
UNIQUEEinfügen einer Zeile, deren E-Mail bereits in der Tabelle existiert.
PRIMARY KEYEinfügen einer Zeile mit einer id, die bereits existiert.
FOREIGN KEYEinfügen einer Bestellung für eine customer_id, die nicht existiert.
CHECKEinfügen eines Produkts mit negativem Preis.
-- Versuch, eine Bestellung für customer_id = 999 einzufügen (existiert nicht):
INSERT INTO orders (customer_id, placed_at) VALUES (999, CURRENT_DATE);
-- Ergebnis: FEHLER — Verletzung des Fremdschlüssel-Constraints
-- Versuch, ein Produkt mit negativem Preis einzufügen:
INSERT INTO products (name, price) VALUES ('Widget', -5.00);
-- Ergebnis: FEHLER — Verletzung des Check-Constraints

Fehlermeldungen unterscheiden sich zwischen Datenbanksystemen, aber sie nennen Ihnen immer, welches Constraint verletzt wurde und auf welcher Tabelle. Diese Meldungen sorgfältig zu lesen ist eine wichtige Fähigkeit beim Arbeiten mit SQL.

Constraints können auch mit ALTER TABLE zu einer bestehenden Tabelle hinzugefügt oder daraus entfernt werden.

-- Ein CHECK-Constraint zu einer bestehenden Tabelle hinzufügen
ALTER TABLE products
ADD CONSTRAINT chk_products_price CHECK (price >= 0);
-- Ein UNIQUE-Constraint hinzufügen
ALTER TABLE users
ADD CONSTRAINT uq_users_email UNIQUE (email);
-- Ein FOREIGN KEY-Constraint hinzufügen
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
-- Ein Constraint anhand seines Namens entfernen
ALTER TABLE products
DROP CONSTRAINT chk_products_price;

Datenintegrität ist das Fundament einer vertrauenswürdigen Datenbank. Sie stellt sicher, dass nur korrekte, konsistente und sinnvolle Daten in das System gelangen können - automatisch, auf Datenbankebene, für jede Anwendung, die sie nutzt.

ConstraintKategorieWas es durchsetzt
PRIMARY KEYEntitätEindeutige, nicht-null Kennung für jede Zeile
NOT NULLDomäneSpalte muss immer einen Wert enthalten
UNIQUEEntität / DomäneKeine doppelten Werte in einer Spalte
FOREIGN KEYReferenziellVerweise müssen auf existierende Zeilen zeigen
CHECKDomäne / BenutzerdefiniertWerte müssen eine logische Bedingung erfüllen
DEFAULTDomäneErsatzwert, wenn kein Wert angegeben wird

Lernergebnisse: Was Sie nach diesem Kapitel können sollten

Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“

Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:

  • Nennen: die Kategorien der Datenintegrität (Entitäts-, referenzielle und Domänen-Integrität) und gängige Constraints nennen.
  • Erklären: erklären, warum Datenintegrität für die Datenqualität wesentlich ist.
  • Anwenden: Integritäts-Constraints (PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE, CHECK) definieren, benennen, hinzufügen und entfernen.
  • Analysieren: eine Constraint-Verletzung anhand einer Fehlermeldung analysieren und ihre Ursache bestimmen.
  • Beurteilen: beurteilen, welche Constraints für ein gegebenes Datenmodell die Integrität sichern.