Zum Inhalt springen
SQL Anfänger 30 min

Constraints & Schlüssel

Meistere Constraints: PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK und DEFAULT - für sichere und konsistente Daten.

Aktualisiert:

Constraints (Einschraenkungen) sind Regeln, die die Datenbank automatisch durchsetzt. Sie schuetzen deine Daten vor Fehlern und Inkonsistenzen. In diesem Tutorial lernst du alle wichtigen Constraints im Detail kennen.

Warum Constraints?

Ohne Constraints koennte jemand folgendes tun:

-- Einen Kunden ohne Namen anlegen
INSERT INTO kunden (id) VALUES (99);

-- Zwei Kunden mit der gleichen E-Mail
INSERT INTO kunden (id, name, email) VALUES (10, 'A', 'gleich@mail.com');
INSERT INTO kunden (id, name, email) VALUES (11, 'B', 'gleich@mail.com');

-- Eine Bestellung fuer einen nicht existierenden Kunden
INSERT INTO bestellungen (id, kunden_id, betrag) VALUES (99, 9999, 50.00);

-- Einen negativen Preis
INSERT INTO produkte (id, name, preis) VALUES (99, 'Test', -10.00);

Constraints verhindern all diese Probleme automatisch!

NOT NULL - Pflichtfelder

NOT NULL stellt sicher, dass eine Spalte immer einen Wert hat:

CREATE TABLE mitarbeiter (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,          -- Pflichtfeld
    email TEXT,                   -- Optional (darf NULL sein)
    abteilung TEXT NOT NULL       -- Pflichtfeld
);

-- Funktioniert:
INSERT INTO mitarbeiter (id, name, abteilung)
VALUES (1, 'Anna', 'IT');

-- Fehler! Name ist Pflichtfeld:
INSERT INTO mitarbeiter (id, abteilung)
VALUES (2, 'Marketing');
-- ERROR: null value in column "name" violates not-null constraint

Wann NOT NULL verwenden?

Verwende NOT NULL fuerErlaube NULL fuer
Name, TitelOptionale Telefonnummer
E-Mail (bei Pflichtregistrierung)Zusaetzliche Infos
PrimaerschluesselOptionale Beschreibung
Fremdschluessel (bei Pflichtbeziehung)Optionale Verknuepfungen

UNIQUE - Eindeutigkeit

UNIQUE stellt sicher, dass keine zwei Zeilen den gleichen Wert in dieser Spalte haben:

CREATE TABLE benutzer (
    id INTEGER PRIMARY KEY,
    benutzername TEXT UNIQUE NOT NULL,
    email TEXT UNIQUE NOT NULL,
    anzeigename TEXT NOT NULL
);

-- Funktioniert:
INSERT INTO benutzer (id, benutzername, email, anzeigename)
VALUES (1, 'anna123', 'anna@example.com', 'Anna');

-- Fehler! Benutzername ist schon vergeben:
INSERT INTO benutzer (id, benutzername, email, anzeigename)
VALUES (2, 'anna123', 'andere@example.com', 'Andere Anna');
-- ERROR: duplicate key value violates unique constraint

UNIQUE ueber mehrere Spalten

CREATE TABLE kurs_bewertung (
    id INTEGER PRIMARY KEY,
    kurs_id INTEGER NOT NULL,
    benutzer_id INTEGER NOT NULL,
    bewertung INTEGER CHECK (bewertung BETWEEN 1 AND 5),
    UNIQUE (kurs_id, benutzer_id)  -- Ein Benutzer kann jeden Kurs nur einmal bewerten
);

PRIMARY KEY - Primaerschluessel

Der PRIMARY KEY identifiziert jeden Datensatz eindeutig. Er ist automatisch NOT NULL und UNIQUE:

-- Einfacher Primaerschluessel
CREATE TABLE laender (
    code CHAR(2) PRIMARY KEY,     -- z.B. 'DE', 'AT', 'CH'
    name TEXT NOT NULL
);

-- Zusammengesetzter Primaerschluessel
CREATE TABLE produkt_lager (
    produkt_id INTEGER,
    lager_id INTEGER,
    menge INTEGER DEFAULT 0,
    PRIMARY KEY (produkt_id, lager_id)
);

Best Practices fuer Primaerschluessel

TypVorteileNachteile
Auto-Increment INTEGEREinfach, schnellKeine fachliche Bedeutung
UUIDGlobal eindeutigGroesser, langsamer
Natuerlicher Schluessel (z.B. Email)Fachlich sinnvollKann sich aendern

Empfehlung: Nutze eine Auto-Increment INTEGER ID als Primaerschluessel. Das ist in 95% der Faelle die beste Wahl.

FOREIGN KEY - Fremdschluessel

Fremdschluessel stellen sicher, dass Verweise auf andere Tabellen gueltig sind:

CREATE TABLE abteilungen (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL UNIQUE
);

CREATE TABLE mitarbeiter (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    abteilung_id INTEGER,
    FOREIGN KEY (abteilung_id) REFERENCES abteilungen(id)
);

-- Funktioniert (Abteilung existiert):
INSERT INTO abteilungen (id, name) VALUES (1, 'IT');
INSERT INTO mitarbeiter (id, name, abteilung_id) VALUES (1, 'Anna', 1);

-- Fehler (Abteilung 99 existiert nicht):
INSERT INTO mitarbeiter (id, name, abteilung_id) VALUES (2, 'Max', 99);
-- ERROR: insert or update on table "mitarbeiter" violates foreign key constraint

ON DELETE und ON UPDATE Aktionen

Was passiert, wenn der referenzierte Datensatz geloescht oder geaendert wird?

CREATE TABLE bestellungen_v2 (
    id INTEGER PRIMARY KEY,
    kunden_id INTEGER,
    betrag DECIMAL(10,2),

    -- Verschiedene Aktionen:
    FOREIGN KEY (kunden_id) REFERENCES kunden(id)
        ON DELETE CASCADE           -- Bestellung wird mitgeloescht
        ON UPDATE CASCADE           -- ID-Aenderung wird uebernommen
);
AktionBei DELETEBei UPDATE
CASCADEAbhaengige Zeilen werden geloeschtFremdschluessel wird aktualisiert
SET NULLFremdschluessel wird NULLFremdschluessel wird NULL
SET DEFAULTFremdschluessel wird DEFAULTFremdschluessel wird DEFAULT
RESTRICTLoeschen wird verhindertAendern wird verhindert
NO ACTIONWie RESTRICT (Standard)Wie RESTRICT (Standard)

SQLite: Fremdschluessel aktivieren

In SQLite sind Fremdschluessel standardmaessig deaktiviert. Aktiviere sie mit:

PRAGMA foreign_keys = ON;

CHECK - Wertebedingungen

CHECK definiert Regeln, die Werte erfuellen muessen:

CREATE TABLE artikel (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    preis DECIMAL(10,2) CHECK (preis > 0),
    rabatt DECIMAL(3,2) CHECK (rabatt >= 0 AND rabatt <= 1),
    lagerbestand INTEGER CHECK (lagerbestand >= 0),
    kategorie TEXT CHECK (kategorie IN ('Elektronik', 'Kleidung', 'Lebensmittel', 'Sonstiges'))
);

-- Funktioniert:
INSERT INTO artikel (id, name, preis, rabatt, lagerbestand, kategorie)
VALUES (1, 'Laptop', 999.99, 0.10, 15, 'Elektronik');

-- Fehler! Preis muss positiv sein:
INSERT INTO artikel (id, name, preis, lagerbestand, kategorie)
VALUES (2, 'Test', -5.00, 10, 'Elektronik');
-- ERROR: new row violates check constraint

CHECK ueber mehrere Spalten

CREATE TABLE veranstaltungen (
    id INTEGER PRIMARY KEY,
    titel TEXT NOT NULL,
    start_datum DATE NOT NULL,
    end_datum DATE NOT NULL,
    CHECK (end_datum >= start_datum)  -- Ende darf nicht vor dem Start sein
);

DEFAULT - Standardwerte

DEFAULT setzt automatisch einen Wert, wenn keiner angegeben wird:

CREATE TABLE aufgaben (
    id INTEGER PRIMARY KEY,
    titel TEXT NOT NULL,
    beschreibung TEXT DEFAULT '',
    status TEXT DEFAULT 'offen',
    prioritaet INTEGER DEFAULT 3,
    erstellt_am TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    faellig_am DATE DEFAULT (CURRENT_DATE + 7)  -- PostgreSQL: in 7 Tagen
);

INSERT INTO aufgaben (id, titel) VALUES (1, 'SQL lernen');
-- status = 'offen', prioritaet = 3, erstellt_am = jetzt

Constraints benennen

Du kannst Constraints benennen, was Fehlermeldungen klarer macht:

CREATE TABLE vertraege (
    id INTEGER PRIMARY KEY,
    kunde_id INTEGER NOT NULL,
    betrag DECIMAL(10,2) NOT NULL,
    start_datum DATE NOT NULL,
    end_datum DATE,

    CONSTRAINT fk_kunde
        FOREIGN KEY (kunde_id) REFERENCES kunden(id),

    CONSTRAINT chk_betrag_positiv
        CHECK (betrag > 0),

    CONSTRAINT chk_datum_gueltig
        CHECK (end_datum IS NULL OR end_datum > start_datum)
);

Benannte Constraints machen Fehlermeldungen lesbarer:

  • Ohne Name: “violates check constraint”
  • Mit Name: “violates check constraint chk_betrag_positiv

Praxisbeispiel: E-Commerce Schema

-- Kunden mit allen wichtigen Constraints
CREATE TABLE shop_kunden (
    id INTEGER PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    passwort_hash TEXT NOT NULL,
    telefon VARCHAR(20),
    registriert_am TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    aktiv BOOLEAN DEFAULT TRUE,

    CONSTRAINT chk_email_format
        CHECK (email LIKE '%_@_%.__%')
);

-- Produkte mit Preisvalidierung
CREATE TABLE shop_produkte (
    id INTEGER PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    beschreibung TEXT,
    preis DECIMAL(10,2) NOT NULL,
    vergleichspreis DECIMAL(10,2),
    lagerbestand INTEGER DEFAULT 0,

    CONSTRAINT chk_preis_positiv CHECK (preis > 0),
    CONSTRAINT chk_lager_nicht_negativ CHECK (lagerbestand >= 0),
    CONSTRAINT chk_vergleichspreis CHECK (vergleichspreis IS NULL OR vergleichspreis >= preis)
);

Was kommt als Naechstes?

Im naechsten Tutorial lernst du ALTER TABLE kennen - damit aenderst du bestehende Tabellen, fuegest Spalten hinzu oder entfernst sie.

Zusammenfassung

  • NOT NULL erzwingt, dass eine Spalte immer einen Wert hat
  • UNIQUE verhindert doppelte Werte in einer Spalte
  • PRIMARY KEY = UNIQUE + NOT NULL, identifiziert Datensaetze eindeutig
  • FOREIGN KEY erzwingt gueltige Verweise zwischen Tabellen
  • CHECK definiert benutzerdefinierte Bedingungen fuer Werte
  • DEFAULT setzt automatische Standardwerte
  • Benenne Constraints fuer bessere Fehlermeldungen

Uebungen

  1. NOT NULL und UNIQUE: Erstelle eine Tabelle emails mit einer eindeutigen, nicht leeren E-Mail-Adresse.
  2. FOREIGN KEY: Erstelle die Tabellen teams und spieler, wobei jeder Spieler zu einem Team gehoert. Was passiert, wenn du ein Team loeschst?
  3. CHECK: Erstelle eine Tabelle bewertungen mit einer Bewertung von 1-5 und einem Kommentar, der mindestens 10 Zeichen lang sein muss.
  4. Kombination: Erstelle eine vollstaendige benutzer-Tabelle mit allen sinnvollen Constraints.
  5. Fehlerfinden: Welche Constraints fehlen in dieser Tabelle?
    CREATE TABLE konten (
        id INTEGER,
        inhaber TEXT,
        saldo DECIMAL(10,2),
        typ TEXT
    );
-- Loesung zu Uebung 3:
CREATE TABLE bewertungen (
    id INTEGER PRIMARY KEY,
    produkt_id INTEGER NOT NULL REFERENCES produkte(id),
    benutzer_id INTEGER NOT NULL,
    bewertung INTEGER NOT NULL CHECK (bewertung BETWEEN 1 AND 5),
    kommentar TEXT CHECK (LENGTH(kommentar) >= 10),
    erstellt_am TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (produkt_id, benutzer_id)
);

Pro-Tipp: Setze Constraints so frueh wie moeglich - am besten direkt beim Erstellen der Tabelle. Constraints nachtraeglich hinzuzufuegen ist moeglich, aber schwieriger, wenn bereits fehlerhafte Daten in der Tabelle sind. Denke an Constraints als dein Sicherheitsnetz: Sie fangen Fehler ab, bevor sie zu Problemen werden.

Zurück zum SQL Kurs