Einführung in SQL: Fremdschlüssel-Beziehungen

Aus Wikibooks

Wechseln zu: Navigation, Suche


In diesem Kapitel werden wir die Verknüpfungen zwischen Tabellen über Fremdschlüssel behandeln.

Inhaltsverzeichnis

[Bearbeiten] Definition

Auf der Ebene von Tabellen bezeichnet man die Tabelle, auf deren Primärschlüssel verwiesen wird, auch als Parent-Tabelle. Die Tabelle, die den Fremdschlüssel enthält, bezeichnet man als Child-Tabelle. In der Literatur werden auch manchmal die deutschen Bezeichnungen Eltern-Tabelle und Kind-Tabelle oder Mastertabelle und Detailtabelle verwendet.

Für Aktualisierungen des Primärschlüssels in der Mastertabelle sind folgende Optionen möglich:

  • ON UPDATE CASCADE (Aktualisierungsweitergabe) bedeutet, dass die Aktualisierung auch an den Fremdschlüsseln der Detailtabelle vorgenommen wird.
  • ON UPDATE RESTRICT (Aktualisierungsrestriktion) bedeutet: Wenn ein Schlüssel in der Mastertabelle geändert werden soll, und es existieren abhängige Sätze in der Detailtabelle, dann wird die Änderung verweigert.

Für Löschungen in der Mastertabelle sind folgende Optionen möglich:

  • ON DELETE CASCADE (Löschweitergabe) bedeutet, es werden auch alle Sätze in der Detailtabelle gelöscht, die auf diesen Schlüssel referenzieren.
  • ON DELETE RESTRICT (Löschrestriktion) bedeutet: wenn ein Satz in der Mastertabelle gelöscht werden soll, und es existieren abhängige Sätze in der Detailtabelle, dann wird die Löschung verweigert.
  • ON DELETE SET NULL (Nullifies) bedeutet: wenn ein Satz in der Mastertabelle gelöscht werden soll, und es existieren abhängige Sätze in der Detailtabelle, dann wird in diese Fremdschlüssel NULL eingetragen. Voraussetzung ist hier, dass der Fremdschlüssel als optional-Spalte in der Tabelle definiert ist. (nicht mit NOT NULL)

Die verschiedenen Update- und Lösch-Optionen werden nicht von allen RDBMS unterstützt. Die Option ON UPDATE CASCADE z.B. wird von den meisten RDBMS nicht angeboten.

Für die Detailtabelle bedeutet ein Fremdschlüssel die Restriktion, dass in diese Spalten nur Werte eingefügt (INSERT / UPDATE) werden dürfen, die in der Mastertabelle auch vorkommen. Einzige Ausnahme ist, wenn die Fremdschlüssel-Spalte als Optional-Spalte definiert ist. Dann kann hier auch NULL eingefügt werden, obwohl NULL niemals als Primärschlüssel in der Mastertabelle stehen wird.


[Bearbeiten] Beispiel

Annahme, es wurden zwei Tabellen wie folgt in einer Datenbank angelegt:

Kunden
\underline {Kundennr\_pk} Name Ort Bestellung
1 Meier AG Marburg
2 Müller Bank Waldhausen *
3 Fa. Schaffel Woauchimmer
4 Kinder KG Kleckerdorf *
Bestellungen
Bestellnr \overline {Kundennr\_fk}
1 2
2 4

Weiter wurde definiert, dass die Kundennr in der Tabelle Kunden der Primärschlüssel und in der Tabelle Bestellungen der Fremdschlüssel (Foreign Key) ist.

Das Datenbankmanagementsystem sorgt dafür, dass:

  • keine neue Bestellung ohne vorhandene Kundennr eingetragen werden kann.
  • keine Kundensätze gelöscht werden können solange noch Bestellungen vorliegen. (Mit * markiert). Wurde allerdings die Löschweitergabe (DELETE CASCADE) eingestellt, werden alle Bestellsätze zum Kunden und der Kundensatz gelöscht.
  • keine Kundennummern geändert werden können, ohne die abhängigen Bestellsätze mitzuändern (Update-Weitergabe) oder dass Änderungen an Kundennummern (bei den mit * markierten Sätzen) erst gar nicht zugelassen werden (Update-Restriktion).

SQL-Kommando zum Erstellen der Beziehung
Crystal Clear app terminal.png SQL-Quelltext:

ALTER TABLE Bestellungen
ADD CONSTRAINT fk_bestellungen_kundennr FOREIGN KEY (kundennr_fk)
REFERENCES Kunden (kundennr_pk)
ON UPDATE RESTRICT
ON DELETE RESTRICT

[Bearbeiten] Fremdschlüssel aus mehreren Attributen

Primärschlüssel und Fremdschlüssel können auch aus mehreren Spalten bestehen. Beispiel:

Crystal Clear app terminal.png SQL-Quelltext:

CREATE TABLE p
( p1 INTEGER NOT NULL
, p2 INTEGER NOT NULL
, PRIMARY KEY (p1, p2)
)
;
CREATE TABLE d
( d1 INTEGER NOT NULL
, d2 INTEGER 
, d3 INTEGER 
, PRIMARY KEY (d1)
, FOREIGN KEY (d2, d3) REFERENCES p ON DELETE CASCADE
)
;
INSERT INTO p VALUES (1, 1);
INSERT INTO p VALUES (1, 2);
INSERT INTO p VALUES (2, 1);
INSERT INTO p VALUES (2, 2);
INSERT INTO d VALUES (1, 1, 1);
INSERT INTO d VALUES (2, 1, 1);
INSERT INTO d VALUES (3, 1, 2);

Wenn die Fremdschlüssel-Spalten auch NULL-Werte enthalten dürfen, dann findet keine Fremdschlüssel-Prüfung statt, sobald der Fremdschlüssel NULL-Werte enthält.

Crystal Clear app terminal.png SQL-Quelltext:

INSERT INTO d VALUES (4, NULL, NULL);
INSERT INTO d VALUES (5, 1, NULL);
INSERT INTO d VALUES (6, NULL, 1);

Das gilt auch dann, wenn nur eine der Fremdschlüssel-Spalten einen NULL-Wert enthält. In diesem Fall kann es passieren, dass die anderen Spalten des Fremdschlüssels Werte erhalten, die es in der Parent-Table gar nicht gibt. Das angegebene Beispiel liefert bei Oracle 10g und DB2 V9 keinen Fehler.

Crystal Clear app terminal.png SQL-Quelltext:

INSERT INTO d VALUES (7, NULL, 99);
INSERT INTO d VALUES (8, 99, NULL);

[Bearbeiten] Kombination von Fremdschlüsseln

Grundsätzlich kann eine Detailtabelle gleichzeitig als Mastertabelle für eine andere Tabelle definiert werden. Dann sind aber nicht alle Kombinationen von Lösch-Weitergabe und Lösch-Restriktion zulässig (Ebenso bei der Update-Option). Das RDBMS prüft beim Ausführen der DDL-Befehle, ob die gewünschte Regel zulässig ist.

ist nicht zulässig:

  Tabelle A (Mastertabelle)
     ^
     | mit Löschweitergabe
     |
  Tabelle B (Detailtabelle von A)
     ^
     | mit Lösch-Restriktion
     |
  Tabelle C (Detailtabelle von B)


ist zulässig:

  Tabelle A (Mastertabelle)
     ^
     | mit Lösch-Restriktion
     |
  Tabelle B (Detailtabelle von A)
     ^
     | mit Löschweitergabe
     |
  Tabelle C (Detailtabelle von B)

[Bearbeiten] rekursive Fremdschlüssel

Fremdschlüsselbeziehungen können auch rekursiv definiert werden. Dabei verweist eine abhängige Spalte einer Tabelle auf den Primärschlüssel der eigenen Tabelle.

Beispiel

Crystal Clear app terminal.png SQL-Quelltext:

CREATE TABLE Abteilung
( AbtNr INTEGER NOT NULL
, UebergeordneteAbt integer 
, AbtName VARCHAR(100)
, PRIMARY KEY(AbtNr)
, FOREIGN KEY (UebergeordneteAbt)
  REFERENCES Abteilung(AbtNr)
  ON DELETE CASCADE
)

Rekursive Fremdschlüsselbeziehungen sind etwas problematisch in der Handhabung. Wenn Sätze eingefügt werden, dann muss das in einer bestimmten Reihenfolge geschehen.

Wenn Sätze gelöscht werden, dann kann es - bei Löschweitergabe - zur Löschung von wesentlich mehr Daten kommen, als in der WHERE-Bedingung angegeben:

Crystal Clear app terminal.png SQL-Quelltext:

DELETE FROM Abteilung
 WHERE AbtNr = 33

Es werden bei diesem Beispiel auch alle Sätze gelöscht, die der Abteilung 33 untergeordnet sind.

Wenn der Fremdschlüssel mit Lösch-Restriktion definiert wurde, dann wird nach jeder Löschung eines einzelnen Satzes geprüft, ob es keine Fremdschlüsselverletzung gibt. Selbst wenn alle Sätze aus der Tabelle entfernt werden sollen, kann es passieren, dass die Ausführung fehlschlägt.

Crystal Clear app terminal.png SQL-Quelltext:

DELETE FROM Abteilung

Das liegt daran, dass bei der Ausführung des DELETE-Statements die Sätze in einer beliebigen Reihenfolge gelöscht werden, meistens in der Reihenfolge, in der sie im Tablespace gespeichert sind. Nur wenn die Sätze exakt in der richtigen Reihenfolge (von der untersten Abteilung beginnend bis zur obersten Abteilung) gespeichert sind, dann kann die Ausführung des DELETE-Statements gelingen.

Dass der Erfolg eines SQL-Statements von der physischen Speicherreihenfolge der Sätze abhängig ist, darf in einem RDBMS nicht vorkommen. Daher bieten einige RDBMS die Möglichkeit der verzögerten Prüfung bei der Löschweitergabe.

Durch ein einziges DELETE-Statement können mehrere Sätze evtl. auch alle Sätze einer Tabelle gelöscht werden. Innerhalb einer Transaktion können mehrere DELETE-Statements ausgeführt werden. Standardmäßig wird die Prüfung, ob eine Lösch-Operation ausgeführt werden darf, ausgeführt, nach jedem einzelnen Satz, der gelöscht wurde. Das hat den Vorteil, dass bei einer unzulässigen Löschung gleich abgebrochen werden kann und der Rollback nicht unnötig viel zu tun hat.

Um die oben beschriebenen Wikipedia-logo.png Lösch-Anomalien zu vermeiden, bieten einige RDBMS die Möglichkeit, die Prüfung, ob die Löschung zulässig ist, nicht nach jedem einzelnen Satz auszuführen, sondern

  • nach der Löschung aller Sätze, die durch ein DELETE-Statement angewiesen werden (Diese Variante wird z.B. von DB2 angeboten durch die Option ON DELETE NO ACTION)
  • erst zum Abschluss der Transaktion auszuführen. (Diese Variante wird z.B. von ORACLE angeboten durch die Option INITIALLY IMMEDIATE DEFERRABLE)
  • einige RDBMS bieten auch die Möglichkeit, Fremdschlüssel-Beziehungen zu deaktivieren und später wieder zu aktivieren. Bei einer Aktivierung muss der gesamte Datenbestand der betroffenen Tabelle überprüft werden und es müssen Anweisungen erteilt werden, wie mit fehlerhaften Sätzen umgegangen werden soll.
  • Mit Tools (z.B. Import, Load) kann man bei den meisten RDBMS Sätze in eine Tabelle laden ohne dabei die Fremdschlüssel-Beziehungen zu prüfen. Bei DB2 z.B. ist die Tabelle danach gesperrt und muss durch das CHECK-Tool geprüft werden. Erst dann steht die Tabelle wieder für reguläre Zugriffe zur Verfügung.
  • wieder andere RDBMS lassen rekursive Fremdschlüsselbeziehungen erst gar nicht zu.

[Bearbeiten] Einfüge- und Lösch-Reihenfolge bestimmen

Wenn die Tabellen in einer Datenbank mit Fremdschlüssel-Beziehungen verbunden sind, dann muss zum Befüllen der Tabellen eine bestimmte Reihenfolge eingehalten werden. Man muss mit den Tabellen beginnen, die keine Fremdschlüssel haben. Danach können die zu diesen Tabellen untergeordneten Tabellen befüllt werden und so weiter. Zum Löschen von Datensätzen muss - sofern nicht mit einer automatischen Lösch-Weitergabe gearbeitet wird - genau die umgekehrte Reihenfolge eingehalten werden.

Wenn Ring-Verkettungen vorkommen, dann müssen Tools zum initialen Befüllen verwendet werden oder es muss mit Sätzen begonnen werden, die null als Fremdschlüssel enthalten.

Bei großen Datenmodellen lohnt es sich, sich alle vorhandenen Tabellen in der Reihenfolge zu notieren, in der sie befüllt werden können.

Theoretisch handelt es sich hier um ein Problem der Wikipedia-logo.png topologischen Sortierung.


SQL-Statement zur Bestimmung der Einfüge-Reihenfolge bei Tabellen, die mit Fremdschlüssel verbunden sind. Voraussetzung ist, dass die Tabellen bereits erstellt sind, denn dann kann man aus dem Datenbank-Katalog die Beziehungen entnehmen.


[Bearbeiten] Beispiel für DB2

(DB2 für zOS und DB2 für Linux, Unix, Windows)

Crystal Clear app terminal.png SQL-Quelltext:

WITH rel(child, parent) AS
   (
      SELECT
         tbname    child
       , reftbname parent
        FROM sysibm.sysrels
       WHERE creator = 'P123'
         AND reftbcreator = 'P123'
   ) ,
ebenen ( level, tabelle) AS
   (
      SELECT 1, parent
        FROM rel
       WHERE parent NOT IN (SELECT child FROM rel)
       UNION ALL
      SELECT level+1, child
        FROM rel, ebenen
       WHERE ebenen.tabelle = rel.parent
         AND level < 100
   )
-- Ermitteln der Ebenen 1 bis 100
SELECT MAX(level) ebene, tabelle
FROM ebenen
GROUP BY tabelle
-- Hinzufügen der Tabellen aus Cycles mit mehr als einem Element
UNION ALL
SELECT DISTINCT 200 ebene, name tabelle
FROM sysibm.systables
WHERE creator = 'P123'
AND type = 'T'
AND name NOT IN (SELECT tabelle FROM ebenen)
AND name IN (SELECT parent FROM rel)
-- Hinzufügen aller anderen Tabellen ohne RI
UNION ALL
SELECT 300 ebene, name tabelle
FROM sysibm.systables
WHERE creator = 'P123'
AND type = 'T'
AND name NOT IN (SELECT parent FROM rel
           UNION SELECT child  FROM rel)
ORDER BY 1, 2
;

[Bearbeiten] Beispiel für ORACLE

getestet mit Oracle Version 10g

WITH rel AS 
( 
-- rel start 
SELECT 
  child_c.table_name child 
, parent_c.table_name parent 
FROM  user_constraints child_c, user_constraints parent_c 
WHERE child_c.constraint_type = 'R' 
AND   child_c.r_constraint_name = parent_c.constraint_name 
-- rel ende 
) 
, ebenen AS 
( 
-- ebenen start 
SELECT level+1 ebene, child tabelle 
FROM rel 
CONNECT BY PRIOR child = parent 
START WITH parent IN 
( 
SELECT parent 
FROM rel 
WHERE parent NOT IN (SELECT child FROM rel) 
) 
UNION 
-- Ebene 1 hinzufügen 
SELECT 1, parent 
FROM rel 
WHERE parent NOT IN (SELECT child FROM rel) 
UNION 
-- Ebene 100 für Cycle mit nur einem Element hinzufügen 
SELECT 100, parent 
FROM rel 
WHERE parent = child 
-- ebenen ende 
) 
 
 
-- Ermitteln der Ebenen 1 bis 100 
SELECT MAX(ebene) ebene, tabelle 
FROM ebenen 
GROUP BY tabelle 
-- Hinzufügen der Tabellen aus Cycles mit mehr als einem Element 
UNION ALL 
SELECT DISTINCT 200 ebene, table_name tabelle 
FROM user_tables 
WHERE table_name NOT IN (SELECT tabelle FROM ebenen) 
AND   table_name IN (SELECT parent FROM rel) 
-- Hinzufügen aller anderen Tabellen ohne RI 
UNION ALL 
SELECT 300 ebene, table_name tabelle 
FROM user_tables 
WHERE table_name NOT IN (SELECT parent FROM rel 
                   UNION SELECT  child FROM rel) 
ORDER BY 1, 2 
;

[Bearbeiten] Erläuterung

  • Wenn man alle Tabellen befüllen will, dann muss man mit der Ebene 1 beginnen. Danach kommt die Ebene 2 dran und so weiter.
  • Auf Ebene 100 werden Cycle mit nur einem Element ausgegeben.
  • Auf Ebene 200 werden Cycle mit mehr als einem Element ausgegeben.
  • Auf Ebene 300 werden alle Tabellen ausgegeben, die überhaupt keine Beziehungen zu anderen Tabellen haben.

Diese Lösung für Oracle ist ziemlich inperformant. Sie funktioniert nur gut, wenn es sich um eine geringe Anzahl von Tabellen handelt. Wenn man es mit vielen von einander abhängigen Tabellen zu tun hat, dann wird die Performens besser, wenn man anstelle der Inline-Views richtige Tabellen definiert und durch geeignete Indices ergänzt.


[Bearbeiten] Zusammenfassung


[Bearbeiten] Übungen


Persönliche Werkzeuge