11. Datenintegrität
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
NULLund keinen Buchstaben. Korrektheit wird durch Datentypen,NOT NULLundCHECK-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 dasstart_datein derselben Zeile, ist inkonsistent. Konsistenz wird durchFOREIGN KEY-Constraints und mehrspaltigeCHECK-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.
Warum Datenintegrität wichtig ist
Abschnitt betitelt „Warum Datenintegrität wichtig ist“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:
| Vorteil | Erläuterung |
|---|---|
| Zuverlässigkeit | Daten sind im gesamten System konsistent und vertrauenswürdig. |
| Automatisierung | Die Datenbank setzt Regeln automatisch durch und entlastet so den Anwendungscode. |
| Frühe Fehlererkennung | Ungültige Daten werden sofort abgewiesen, bevor sie Schaden anrichten können. |
| Interoperabilität | Mehrere Anwendungen, die dieselbe Datenbank nutzen, profitieren alle von denselben Regeln. |
Kategorien der Datenintegrität
Abschnitt betitelt „Kategorien der Datenintegrität“Datenintegrität wird traditionell in vier Kategorien unterteilt. Jede behandelt eine andere Dimension der Datenqualität.
Entitätsintegrität
Abschnitt betitelt „Entitätsintegritä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);-- Die Spalte id identifiziert jeden Kunden eindeutig.CREATE TABLE customers ( id INT AUTO_INCREMENT 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.
Referenzielle Integrität
Abschnitt betitelt „Referenzielle Integrität“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);-- Eine Bestellung muss auf einen existierenden Kunden verweisen.CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, placed_at DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES customers(id));Wenn jemand versucht, eine Bestellung mit einer customer_id einzufügen, die nicht in customers existiert, weist die Datenbank die Operation sofort zurück.
Domänenintegrität
Abschnitt betitelt „Domänenintegrität“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 TypDATEweist den Text"hello"zurück. - Eine
price-Spalte mitCHECK (price >= 0)weist-5.00zurück. - Eine
status-Spalte mitCHECK (status IN ('ACTIVE', 'INACTIVE'))weist jede andere Zeichenkette zurück.
Benutzerdefinierte Integrität
Abschnitt betitelt „Benutzerdefinierte Integrität“Ü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 zwischen0und100liegen. - Ein
start_datedarf nicht nach einemend_dateliegen. - Eine
quantityin 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.
Integritäts-Constraints in SQL
Abschnitt betitelt „Integritäts-Constraints in SQL“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.
NOT NULL
Abschnitt betitelt „NOT NULL“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));CREATE TABLE employees ( id INT AUTO_INCREMENT 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);-- MariaDB/MySQL benötigt VARCHAR (nicht TEXT) für UNIQUE-Constraints,-- weil Index-Schlüssel auf TEXT-Spalten eine explizite Präfixlänge brauchen.CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE);PRIMARY KEY
Abschnitt betitelt „PRIMARY KEY“Das PRIMARY KEY-Constraint ist die zentrale Säule der Entitätsintegrität. Es vereint zwei Regeln in einer:
- Die Spalte (oder Spalten) muss eindeutig sein.
- Die Spalte (oder Spalten) darf niemals
NULLsein.
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üsselCREATE 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));-- Einspaltiger PrimärschlüsselCREATE TABLE products ( product_id INT AUTO_INCREMENT PRIMARY KEY, name TEXT NOT NULL);
-- Zusammengesetzter Primärschlüssel (häufig in Zwischentabellen)CREATE TABLE course_registrations ( student_id INT NOT NULL, course_id INT NOT NULL, PRIMARY KEY (student_id, course_id));FOREIGN KEY und referenzielle Aktionen
Abschnitt betitelt „FOREIGN KEY und referenzielle Aktionen“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:
| Aktion | Wirkung |
|---|---|
RESTRICT | Die Operation auf der Elternzeile wird abgewiesen, wenn Kindzeilen existieren. Das ist der Standard in den meisten Systemen. |
CASCADE | Die Operation wird automatisch auf alle Kindzeilen weitergegeben (z. B. werden sie gelöscht oder aktualisiert). |
SET NULL | Die Fremdschlüsselspalten in den Kindzeilen werden auf NULL gesetzt. |
SET DEFAULT | Die 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);CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, placed_at DATE NOT NULL, CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE RESTRICT ON UPDATE CASCADE);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 dieideines Kunden ändert (selten, aber möglich), werden alle passendencustomer_id-Werte inordersautomatisch 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));CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name TEXT NOT NULL, price DECIMAL(10, 2) NOT NULL CHECK (price >= 0), discount DECIMAL(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));CREATE TABLE projects ( id INT AUTO_INCREMENT 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.
DEFAULT
Abschnitt betitelt „DEFAULT“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);CREATE TABLE articles ( id INT AUTO_INCREMENT PRIMARY KEY, title TEXT NOT NULL, published BOOLEAN NOT NULL DEFAULT FALSE, created_at DATETIME 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.
Constraints benennen
Abschnitt betitelt „Constraints benennen“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));CREATE TABLE order_items ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(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üsseluq_<table>_<column>- Unique-Constraintfk_<table>_<referenced_table>- Fremdschlüsselchk_<table>_<column>- Check-Constraint
Constraint-Verletzungen
Abschnitt betitelt „Constraint-Verletzungen“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:
| Constraint | Beispiel einer Verletzung |
|---|---|
NOT NULL | Einfügen einer Zeile ohne Wert für eine erforderliche Spalte. |
UNIQUE | Einfügen einer Zeile, deren E-Mail bereits in der Tabelle existiert. |
PRIMARY KEY | Einfügen einer Zeile mit einer id, die bereits existiert. |
FOREIGN KEY | Einfügen einer Bestellung für eine customer_id, die nicht existiert. |
CHECK | Einfü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-ConstraintsFehlermeldungen 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 hinzufügen und entfernen
Abschnitt betitelt „Constraints hinzufügen und entfernen“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ügenALTER TABLE productsADD CONSTRAINT chk_products_price CHECK (price >= 0);
-- Ein UNIQUE-Constraint hinzufügenALTER TABLE usersADD CONSTRAINT uq_users_email UNIQUE (email);
-- Ein FOREIGN KEY-Constraint hinzufügenALTER TABLE ordersADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(id);
-- Ein Constraint anhand seines Namens entfernenALTER TABLE productsDROP CONSTRAINT chk_products_price;-- Ein CHECK-Constraint hinzufügenALTER TABLE productsADD CONSTRAINT chk_products_price CHECK (price >= 0);
-- Ein UNIQUE-Constraint hinzufügenALTER TABLE usersADD CONSTRAINT uq_users_email UNIQUE (email);
-- Ein FOREIGN KEY-Constraint hinzufügenALTER TABLE ordersADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(id);
-- Ein CHECK-Constraint entfernenALTER TABLE productsDROP CONSTRAINT chk_products_price;
-- Ein FOREIGN KEY-Constraint entfernenALTER TABLE ordersDROP FOREIGN KEY fk_orders_customer;
-- Ein UNIQUE-Constraint entfernen (in MariaDB/MySQL als Index behandelt)ALTER TABLE usersDROP INDEX uq_users_email;Zusammenfassung
Abschnitt betitelt „Zusammenfassung“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.
| Constraint | Kategorie | Was es durchsetzt |
|---|---|---|
PRIMARY KEY | Entität | Eindeutige, nicht-null Kennung für jede Zeile |
NOT NULL | Domäne | Spalte muss immer einen Wert enthalten |
UNIQUE | Entität / Domäne | Keine doppelten Werte in einer Spalte |
FOREIGN KEY | Referenziell | Verweise müssen auf existierende Zeilen zeigen |
CHECK | Domäne / Benutzerdefiniert | Werte müssen eine logische Bedingung erfüllen |
DEFAULT | Domäne | Ersatzwert, 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.