RELATIONALE DATENBANKEN

Werbung
Bern University of Applied Sciences
Engineering and Information Technology
RELATIONALE DATENBANKEN
P. Fierz
Keywords: relationales Modell, relationale Algebra, Normalisierung, Entity-RelationshipModel, SQL, Transaktionen, Views, jdbc, Datenbankprozeduren, Triggers, objektrelationales
Mapping
[File rdb.tex, Date 14.02.2014, Version 4.1]
© P. Fierz
Zusammenfassung
Dieses Skript gibt einen überblick über das relationale Datenbankmodell. Insbesondere werden
das relationale Modell (1970) von E.F. Codd und das Entity-Relationship-Modell (1976) von
P. Chen beschrieben. Weiter wird die Umsetzung des relationalen Modells in konkrete Datenbansysteme insbesondere auch in der Sprache SQL behandelt. In einem zweiten Teil werden die
Transaktionstheorie behandelt. Schliesslich wird auch die Programmierung in Datenbanken
behandelt mit jdbc, Prozeduren und Triggers und schliesslich auch objektrelationales Mapping
mit jpa.
Kapitel 1
Einführung
Diese Einführung gibt einen raschen und oberflächlichen Einstieg in den ganzen Problembereich der Datenbanken. Die hier erwähnten Begriffe und Konzepte werden in den folgenden
Kapiteln dieses Skriptes genauer erklärt.
1.1
Filesysteme
In den Anfängen der Datenverarbeitung stand das programmgesteuerte Bearbeiten der Daten
– also das Rechnen, Zählen, Schreiben – im Vordergrund. Dazu werden die Daten auf einzelnen
Files (auf Platte oder Band) gespeichert. Der Zugriff auf die Files sowie die interne Organisation und Struktur der einzelenen Datensätzen wird vollständig von der Applikationssoftware
geregelt. Je nach Filesystem werden dazu Hilfen wie Locking und Zugriffsmethoden (ISAM,
B-Trees usw.) vom System zur Verfügung gestellt. Auch die Frage der gemeinsamen Nutzung
von Daten durch mehrere Applikationen muss von diesen selbst geregelt werden (Konsistenz,
Integrität usw). Diese Situation ist in der Abbildung 1-1 schematisch dargestellt.
Zugriffsregelung
Applikation 1
Applikation 2
Applikation 3
Gemeinsame Daten
Daten
Abbildung 1-1: Zugriff auf gemeinsamen Daten in einem Filesystem
1-1
Wir wollen nun kurz die Vor- und Nachteile eines klassischen Filesystems aufzählen.
Vorteile
• Im Allgemeinen ist der Entwicklungsaufwand für eine Applikation geringer.
• Da der Zugriff auf die Daten von der Applikation geregelt wird, ist in manchen
Fällen eine gezielte und gute Optimierung der Zugriffe auf die Daten möglich.
Nachteile
• Die Daten weisen oft viel Redundanz auf.
• Die Daten einer Applikation sind häufig in einer zweiten Applikation nicht (oder
nur schwer) nutzbar, weil die Strukturen nicht übereinstimmen.
• Da jede Applikation den Zugriff auf die Daten selber regelt, ist die Datenintegrität
oft nicht gewährleistet.
• Da die Applikationsprogramme und die Daten fest verdrahtet sind, führen schon
kleine Änderungen in der Datenstruktur zu einem erheblichen Programmieraufwand.
1.2
Definition Datenbank
Im Gegensatz etwa zum Filesystem, wo jede Applikation den Zugriff auf bestehende Daten selber regelt (siehe Abschnitt 1.1), wird bei Datenbanken die Beschreibung der Datenstrukturen
sowie der Zugriff auf die Daten zentralisiert. Zwischen dem Benutzer und den Daten steht
ein Datenverwaltungssystem, das die Daten schützt und verschiedenen Benutzer zugänglich
macht.
Definition 1.1 [Datenbank] Eine Datenbank ist ein System zur Beschreibung,
Speicherung und Wiedergewinnung von umfangreichen Datenmengen, die von mehreren
Anwendungen (– gleichzeitig –) benutzt werden können. Sie besteht aus zwei Hauptteilen:
• Den eigentlichen Daten und
• Dem Datenbank-Management-System, (DBMS) das gemäss einer vorgegebenen Beschreibung Daten speichern, suchen, löschen oder ändern kann.
Datenbank
Applikation1
Daten
Metadaten
Applikation 2
Hilfsdaten
Datenbank−Managementsystem
Applikation 3
Abbildung 1-2: Das DBMS steht zwischen Daten und Benutzern
In der Abbildung 1-2 ist der allgemeine Aufbau einer Datenbank schematisch dargestellt.
Unter Hilfsdaten verstehen wir alle Informationen, die zur Beschreibung der Benutzerdaten
notwendig sind, sowie Zugriffshilfen (B-Trees, Hashtabellen usw.).
1-2
Im folgenden sind die wichtigsten Aufgaben des DBMS zusammengefasst. Das DBMS soll
1. verhindern, dass jeder Benutzer sich mit der inneren Organisation des Datenbestandes
befassen muss,
2. verhindern, dass jeder Benutzer unkontrolliert an die Datenbstände gelangen kann und
damit die Integrität der Daten gefährdet,
3. ermöglichen, dass für die Organisation der Daten günstige Voraussetzungen geschaffen
werden, wobei diese Organisation bei Bedarf intern geändert werden kann, ohne dass
dadurch an den Applikationen etwas verändert werden muss und
4. die Dauerhaftigkeit der gespeicherten Daten garantieren. Das heisst konkret, dass die
Daten nach einem Systemabsturz konsistent wieder hergestellt werden können.
Die Einführung eines zentralen DBMS hat folgende Konsequenzen:
Vorteile
• Zusammenfassung aller sonst mehrfach nötigen Funktionen für Datendefinition,
Datenorganisation, Datenintegrität (Modularisierung).
• Geschützter Zugang zu Einzeldaten.
• Einheitliches Konzept.
• Bessere Entwicklungsfähigkeit.
Nachteile
• Abhängigkeit von zentralen Funktionen und Entscheiden.
• Bereitstellung und vor allem Pflege und Wartung des Datenbank-ManagementSystems (wird zwar heute ab der Stange eingekauft).
1.3
Eigenschaften einer Datenbank
Aus den obigen Überlegungen können wir folgende charakteristische Eigenschaften einer Datenbank ableiten ([Zeh89]). Diese werden durch die im nächsten Abschnitt vorgestellte Datenbankarchitektur auch sehr gut unterstützt.
Strukturierung der Daten Der Datenbestand hat einen überschaubaren inneren Aufbau,
so dass sich ein Benutzer auf bestimmte Daten und Datengruppen beziehen kann. Ungeordnete Mehrfachspeicherung derselben Daten ist nicht möglich.
Trennung der Daten von den Anwendungen Die Trennung der Daten und ihrer physischen Organisation von den Anwendungen ermöglicht ein beidseitig weitgehend unabhängiges Arbeiten. Das bedeutet:
Datenunabhängigkeit Die Anwendungsprogramme sind “datenunabhängig”, d.h. interne Reorganisationen innerhalb des Datenbanksystems tangieren die Anwendungsprogramme nicht (siehe auch 1.5).
1-3
Flexibilität Die Datenbank ist leicht erweiterbar. Neue Bedürfnisse der Benutzer an
die Daten sollen nachträglich befriedigt werden können.
Datenintegrität Die zur Wahrung der Datenintegrität wichtigsten Massnahmen sind die
Eingabekontrolle (Datenkonsistenz), die Datensicherung und der Datenschutz.
Zeitliche Persistenz Die Daten müssen dauerhaft nutzbar sein. Das heisst, sie sind nicht
an die Lebensdauer eines Programms gebunden.
Spezifische Datensicht für verschiedene Benutzer. Der Benutzer muss nach Form und Menge
nur den ihn betreffenden Ausschnitt der Datenbank sehen.
Die angegebenen Eigenschaften sind Zielvorstellungen und in der Praxis nicht in jedem DatenbankManagementsystem realisiert.
Bemerkung 1.1 [Datenbankadministrator] Die Betreuung des zentralen Datensystems
benötigt eine spezielle, qualifizierte Dienstleistung. Mit dieser Aufgabe ist der Datenbankadministrator (DBA) beauftragt.
1.4
Die Architektur von Datenbanksystemen
Im Jahre 1975 veröffentlichte das American National Standard Institut (ANSI) eine Studie
bezüglich der Architektur eines Datenbanksystems. Das vorgeschlagene Konzept ist unter dem
Namen 3-Schemen-Architektur bekannt und ist in der Abbildung 1-3 vereinfacht dargestellt.
DBMS
EDS
(View)
EDS
(View)
Die Welt
(Realität)
EDS
(View)
Externe Ebene
LDS
Logische Ebene
PDS
Interne Ebene
(Physische Ebene)
Speichermedium
Abbildung 1-3: Die 3-Ebenen-Architektur
Das vorgeschlagene Konzept sieht vor, in einem Datenbanksystem drei Ebenen mit unterschiedlichen Aufgaben zu unterstützen. Die Aufgaben der 3 Ebenen sind in der folgenden
Liste erklärt.
1-4
Logische Ebene Auf dieser Ebene wird eine möglichst umfassende logische (das heisst,
hardware- und applikationsunabhängige) Datenstruktur (in Abb. 1-3 LDS genannt) festgehalten. Angestrebt wird die Schaffung eines zentralen stabilen Bezugpunktes, der nur
dann geändert werden muss, wenn der bislang betrachtete Realitätsausschnitt erweitert
oder modifiziert wird.
Über die auf dieser Ebene einzusetzende Strukturart sind nach wie vor Kontroversen
im Gange. Allerdings scheint sich das auf präzisen, mathematisch fundierten Grundlagen basierende Relationenmodell mehr und mehr durchzusetzen. Das Relationenmodell
wird im Kapitel 3 behandelt. Prinzipiell wären aber auf dieser Stufe auch hierarchischeNetzwerk oder objektorientierte Modelle. möglich.
Interne Ebene Auf dieser Ebene ist mit einer sogenannten physischen Datenstruktur (PDS)
festzuhalten, wie die Daten auf einem externen Speichermedium zu speichern sind. Die
physische Datenstruktur wird unter Berücksichtigung der aktuellen Hardwaregegebenheiten von der konzeptionellen Datenstruktur abgeleitet.
Die auf dieser Ebene eingesetzten Strukturarten sind Files, B-Trees, Hashtabellen usw.
Externe Ebene Auf dieser Ebene ist mit Hilfe von sogenannten externen Datenstrukturen
(EDS, auch Views genannt) festzuhalten, wie die Daten einem Benützer der Datenbank
(damit sind sowohl Informatiker wie auch Endbenützer gemeint) zu präsentieren sind.
Auch die externen Datenstrukturen sind – diesmal allerdings unter Berücksichtigung der
applikatorischen Anforderungen – von der konzeptionellen Datenstruktur abzuleiten.
Die auf dieser Ebene eingesetzten Werkzeuge sind sehr vielfältig und reichen von den
konventionellen Datenstrukturen einer Programmiersprache über die interaktive Sprache
SQL (Kapitel 5) bis zu 4GL-Tools.
Bemerkung 1.2 [3-Schemen-Architektur] Es leuchtet ein, dass die 3-Schemen-Architektur,
die im vorigen Abschnitt (1.2) geforderten charakteristischen Eigenschaften einer Datenbank
gut unterstützt.
1.5
Physische Datenunabhängigkeit
Im Abschnitt 1.3 haben wir die Datenunabhängigkeit schon erwähnt. Da diese Eigenschaft
einer der wichtigsten Unterschiede zwischen einem Filesystem und einem Datenbanksystem
darstellt, wird es an dieser Stelle noch einmal genauer erläutert. Vor allem sollen die Vorteile
von datenunabhägigen Applikationen gegenüber datenabhängigen Applikationen (die mit Hilfe eines Filesystems implementiert sind) hervorgehoben werden.
In einer datenabhängigen Applikation, sind Organisation und Zugriff auf die Daten in der
Logik der Programme eingebaut. Dies wollen wir am folgenden Beispiel erläutern.
Beispiel 1.1 [Filesystem] Wir nehmen an, der Kundenfile einer Firma enthalte als Felder
(unter anderem) den Namen und den Umsatz des Kunden. Wir nehmen ferner an, dass über
das Feld Name einen B-Tree existiert.
Will man nun den Kunden “Meyer” suchen, so wird man die Tatsache ausnutzen, dass über
das Feld Name ein B-Tree existiert und ein Befehl der Form
get from KUNDE key = ’Meyer’ with index NAME;
1-5
schreiben. Wird nun der Kundenfile physisch reorganisiert und dabei der B-Tree über den
Namen fallengelassen, so muss der obige Code entsprechend angepasst werden. Dieser Code
ist also nicht datenunabhängig.
Eine weitere Aufgabe ist das Auslisten aller Kunden mit einem Umsatz, der zwischen zwei
gegebenen Zahlen Z1 und Z2 liegt. Die Kunden sollen nach Umsatz sortiert ausgegeben werden.
Der folgende Pseudocode löst diese Aufgabe in einem Filesystem.
getfirst from KUNDE;
while (EOF == false)
{
if (Umsatz >= Z1 && Umsatz <= Z2) write to TEMP;
getnext from KUNDE;
}
sort TEMP;
getfirst from TEMP;
while (EOF == false)
{
print(info);
getnext from TEMP;
}
Das Problem im Beispiel 1.1 ist, dass die Applikation entscheidet, über welche Zugriffswege
die Daten eingelesen werden. Damit eine Applikation “datenunabhängig” wird, muss die Applikation nur noch sagen welche Daten gebraucht werden. Der Zugriffsweg und das Lesen der
Daten ist dann Sache des DBMS.
Beispiel 1.2 [Datenbank] Wir wollen in diesem Beispiel zeigen, wie die Aufgaben aus dem
Beispiel 1.1 mit Hilfe eines DBMS gelöst werden.
Zugriff auf einen Kunden.
select *
from
KUNDE
where Name = ’Meyer’;
Im Unterschied zu vorher wird nicht angegeben, welcher B-Tree verwendet wird. Dieser
Entscheid wird vom Query-Optimizer des DBMS getroffen. Der Code Funktioniert unabhängig davon, ob ein B-Tree über den Namen existiert oder nicht.
Umsatzliste:
select
from KUNDE
where Umsatz >= Z1 and Umsatz <= Z2
order by Umsatz;
loop (ueber alle gefundenen Kunden)
print (info);
Auch hier wird das DBMS entscheiden, wie die Kundensätze gefunden werden sollen (durch
sequentielles Suchen und Sortieren oder über einen B-Tree). Man sieht sofort den Vorteil. Will
man die Verarbeitung verschnellern, so kann ein B-Tree über das Feld Umsatz gelegt werden.
Die Applikation selbst muss nicht verändert werden.
1-6
1.6
Der Datenbank Administrator (DBA)
Verantwortlich für die logische und physische Organisation der Daten in einer Datenbank ist
der Datenbankadministrator (DBA). Diese schwierige Aufgabe kann bei grossen Datenbeständen und vielen verschiedenen Benutzern nur mit Hilfe von guten Statistik- und Optimierungswerkzeugen gelöst werden. Solche Werkzeuge sind so wichtig, dass sie im DBMS integriert sein sollten.
1-7
1-8
Kapitel 2
Das Entity-Relationship Modell
In diesem Abschnitt wollen wir uns mit dem konzeptionellen Datenbankentwurf befassen. Es
geht darum, die Realität zu abstrahieren (d.h. vereinfachen) und nur die für unsere Zwecke
wichtigen Aspekte in einem konzeptionellen Datenmodell festzuhalten. Ein konzeptionelles
Datenmodell muss Hard- und Softwareunabhängig sein und für die Entwickler sowie für die
zukünftigen Anwender eines Systems verständlich sein. Das von Chen 1976 in [Che76] eingeführte Entity-Relationship-Modell erfüllt genau diese Anforderungen. Es wurde speziell zum
Design von relationalen Datenbanken entwickelt.
Bemerkung 2.1 [Datenarchitektur] Das ERM eignet sich besonders gut zum Erstellen
von globalen Datenarchitekturen. Das sind Modelle, die nur die Objekte und die Beziehungen zwischen diesen Objekten darstellen. Die Details der einzelnen Objekte werden zunächst
weggelassen und erst im Verlauf der Applikationsentwicklung in der globalen Architektur
ergänzt (top-down).
2.1
Datenmodellierung
Wir wenden uns nun dem Problem der Datenmodellierung mit Hilfe des E-R-Modelles zu.
Dieser Prozess findet auf einer möglichst hohen Abstraktionsstufe (d.h. Soft- und Hardwareunabhängig) statt und soll uns helfen, einen Ausschnitt der Realität zu modellieren und auch
für Nichtinformatiker verständlich darzustellen. Es ist klar, dass jedes Modell nur einen Teil
(oder gewisse Aspekte) der Realität wiedergeben kann. Daher ist die Wahl des Modells natürlich auch von der Art des Problems abhängig, das man lösen will. Ein allgemein gültiges
Modell existiert nicht.
Das E-R-Modell gibt es in sehr vielen Variationen. Die Grundideen sind aber im wesentlichen
immer dieselben. Daher spielt es keine grosse Rolle, welche Beschreibung und vor allem welche
Darstellungsart gewählt wird.
In den folgenden Abschnitten wird das E-R-Modell vorgestellt. Es wird gezeigt, wie man mit
Hilfe von einfachen Konstruktionselementen die Realität in einer dem menschlichen Verständnis entgegenkommende Weise abbilden kann.
Zur visualisierung des Modells gibt es sehr viele verschidene Schreibweisen. Da die heutige
Software heute meistens mit Hilfe von Klassendiagrammen beschrieben wird, werden wir diese
Notation verwenden.
2-1
2.2
Darstellung von Einzelfällen
Als Beispiel betrachten wir in den folgenden Ausführungen das Informationssystem für ein
Spital. Wir werden an Hand dieses Beispiels die folgenden Konstruktionselemente darstellen:
• Entität,
• Eigenschaft,
• Faktum und
• Beziehung
2.2.1
Entität
Entitäten repräsentieren die für ein Informationssystem relevanten Informationsobjekte.
Definition 2.1 [Entität] Eine Entität ist ein individuelles und identifizierbares
Exemplar von Dingen, Personen oder Begriffen der realen oder der Vorstellungswelt,
für welches applikationsbezogene Informationen von Bedeutung sind.
Eine Entität kann also sein:
• Ein Individuum wie beispielsweise ein Arzt, ein Patient, eine Krankenschwester usw.
• Ein reales Objekt wie beispielsweise ein Operationssaal, ein Krankenzimmer usw.
• Ein abstraktes Konzept wie beispielsweise eine Diagnose, ein Fachgebiet usw.
• Ein Ereignis wie beispielsweise ein Kreislaufkollaps, eine Patientenaufnahme usw.
Aus der Sicht des Modellentwerfers kann eine Entität folgendermassen charakterisiert werden:
• Eine eindeutig identifizierbare Einheit.
• Eine Einheit, deren Existenz auf einem geeigneten Speichermedium aufgrund eines Identifikationsmerkmal darstellbar sein muss (Schlüssel).
• Eine Einheit, für die Informationen zu sammeln und auf einem geeigneten Speichermedium festzuhalten sind.
Diese Merkmale sind bei der Ermittlung der “Ankerpunkte” eines Datenmodells von entscheidender Bedeutung.
2-2
2.2.2
Eigenschaften
Definition 2.2 [Eigenschaft] Eine Eigenschaft wird Entitäten zugeordnet und
ermöglicht damit deren
• Charakterisierung
• Klassierung (vergleiche Abschnitt 2.3)
• Identifizierung (Schlüsseleigenschaften).
Eine Eigenschaft besteht aus einem Namen und einer Menge von Eigenschaftswerten.
Das nächste Beispiel zeigt für einen Arzt und einen Patienten mögliche Eigenschaften und
Eigenschaftswerte.
Beispiel 2.1 [Arzt Patient]
2.2.3
Entität
ein Patient
Eigenschaft
Name
Alter
Gewicht
Sprachen
ein Arzt
Name
Fachgebiet
Eigenschaftswert
Bachmann
33
62
{Deutsch
Englisch
Französisch}
Meier
Innere Medizin
Faktum
Wird einer Entität eine Eigenschaft mit einem Eigenschaftswert zugeordnet, so kommt ein
Faktum zustande.
Definition 2.3 [Faktum] Ein Faktum ist eine Behauptung, derzufolge eine Entität für eine Eigenschaft einen bestimmten Eigenschaftswert aufweist.
Die im Beispiel 2.1 für den Patienten festgehaltenen Eigenschaften und Werte bedeuten auf
die Realität bezogen, dass der Patient Bachmann heisst, 33 Jahre alt ist, ein Gewicht von 62
kp aufweist und die Sprachen Deutsch, Englisch und Französisch spricht.
Bemerkung 2.2 [unterschiedliche Fakten] Man beachte, dass ein und derselbe Eigenschaftswert durchaus mehreren Entitäten zugeordnet werden kann, wodurch entsprechend
viele unterschiedliche Fakten zustande kommen.
2.2.4
Beziehung
An einer Beziehung sind zwei oder mehr Entitäten beteiligt.
Definition 2.4 [Beziehung] Eine Beziehung assoziert wechselseitig zwei (oder
mehr) Entitäten.
2-3
In unserem Beispiel gibt es die Beziehung “Arzt behandelt Patient” und umgekehrt natürlich
auch “Patient wird von Arzt behandelt”. Diese Beziehungen können wir formal folgendermassen festhalten:
behandelt: < Arzt, Patient > und
wird behandelt: < Patient, Arzt >
Berücksichtigen wir dazu auch noch das Behandlungszimmer, so kommt eine Beziehung zwischen drei Entitäten zustande. “Arzt behandelt Patient im Zimmer”. Formal:
behandelt: < Arzt, Patient, Zimmer >
Auch für ein Beziehungselement sind Fakten möglich. Wir können dem Beziehungselement <
Arzt, Patient > die Eigenschaft Krankheit mit dem Wert “Angina” zuweisen. Dieses neue
Faktum bedeutet, dass ein bestimmter Arzt für einen bestimmten Patienten eine Angina
diagnostiziert hat.
2.3
Darstellung von mehreren Fällen
Die Abbildung der Realität nur mit Einzelfällen wäre ausserordentlich mühsam. Daher werden wir in diesem Abschnitt Konstruktionselemente vorstellen, die stellvertretend für viele
Einzelfälle in Erscheinung treten können. Das heisst, wir führen eine neue Abstraktionsebene
ein. Mit den bisherigen Konstruktionselementen können wir Aussagen der Art
Der Arzt Meier behandelt den Patienten Bachmann
machen. Mit den neuen Konstruktionselementen werden abstrakte und kompakte, dennoch
auch für Nichtinformatiker verständliche Datenmodelle definierbar. Mit ihnen können allgemein gültige Aussagen der Art
Ein Arzt hat einen Namen und behandelt mehrere Patienten, die auch
einen Namen haben
formuliert werden.
Die Konstruktionselemente, die stellvertretend für mehrere Einzelfälle stehen, sind:
• Die Entitätsmenge
• Die Domäne (auch Wertebereich genannt)
• Das Entitätsattribut
• Die Beziehungsmenge
• Das Beziehungsattribut
Die Entsprechung der Konstruktionselemente für Einzelfälle und denjenigen für mehrere Fälle
sind in der Abbildung 2-1 dargestellt.
2-4
Konstruktionselemente zur Darstellung von Einzelfaellen
Entitaeten
Eigenschaften/
Werte
Fakten
Beziehungen
Entsprechungen
Entitaetsmengen
Domaenen
Attribute
Beziehungsmengen
Konstruktionselemente zur Darstellung von mehreren Faellen
Abbildung 2-1: Entsprechung Einzelfall – mehrere Einzelfälle
2.3.1
Die Entitätsmenge
Als erstes wollen wir den Typ einer Entität definieren.
Definition 2.5 [Entitätstyp] Die Menge aller Eigenschaften, die eine Entität
charakterisiert, nennt man den Typ der Entität.
Mit Hilfe des Typs einer Entität können wir nun die Entitätsmenge definieren.
Definition 2.6 [Entitätsmenge] Eine eindeutig benannte Menge von Entitäten
des gleichen Typs nennt man Entitätsmenge
Eine Entitätsmenge wird also aufgrund von Eigenschaften und nicht aufgrund von Eigenschaftswerten charakterisiert. Im Spitalbeispiel werden alle Patienten aufgrund der gleichen
Eigenschaften wie Name, Alter und Gewicht charakterisiert und können demzufolge als Entitätsmenge namens Patient aufgefasst werden. Desgleichen werden alle Ärzte in eine Entitätsmenge Arzt zusammengefasst.
Aus dem Arzt-Patienten-Beispiel geht sofort hervor, dass Entitätsmengen überlappen können.
Es ist denkbar, dass ein Arzt zugleich auch Patient sein kann. Um diese Tatsache im Modell
festzuhalten, führt man eine neue Entitätsmenge Person ein, welche sowohl Patienten wie auch
Ärzte umfasst. Man spricht in diesem Zusammenhang von Überlagerung von Entitätsmengen,
die wir im Abschnitt 2.4.1 noch näher beschreiben werden.
Mit der neuen Entitätsmenge Person lässt sich verhindern, dass ein bestimmtes Faktum (z.B.
der Name einer Person) redundant festgehalten wird (beispielsweise für eine Person als Arzt
und für die gleiche Person als Patient).
Am Arzt-Patienten-Beisiel lassen sich auch die Begriffe unabhängige Entität (oder Kernentität)
und abhängige Entität erklären. In unserem Beispiel können Informationen für einen Arzt
nur dann spezifiziert werden, wenn besagter Arzt auch als Person bekannt ist. Dasselbe gilt
natürlich auch für Patienten. In diesem Beispiel sind Personen Kernentitäten, Ärzte und
Patienten abhängige Entitäten.
Definition 2.7 [Kernentität] Eine Kernentität ist eine Entität, deren Existenz
unabhängig anderweitiger Entitäten ist.
2-5
Bei der Realitätsmodellierung sind Entitätsmengen von Kernentitäten – wir nennen diese im
folgenden Kernentitätsmengen – die eigentlichen Modellaufhänger (oder Ankerpunkte). Die
Anzahl der Kernentitätsmengen ist beschränkt und wird auch bei komplexen Datenmodellen
kaum mehr als zehn betragen.
Im Unterschied zu einer Kernentität, die immer eigenständig in Erscheinung tritt, ist die
Existenz einer abhängigen Entität immer von etwas anderem abhängig und kann wie folgt
definiert werden:
Definition 2.8 [abhängige Entität] Eine abhängige Entität ist eine Entität,
deren Existenz von einer anderweitigen Entität (Kern- oder abhängige Entität)
abhängig ist.
2.3.2
Domäne oder Wertebereich
Definition 2.9 [Domäne] Eine Domäne legt eine eindeutig benannte Kollektion
(Menge) der zulässigen Eigenschaftswerte einer Eigenschaft fest.
Durch Angabe der Domäne werden also die möglichen Werte einer Eigenschaft eingeschränkt.
Man kann Domänen auch als Integritätsbedingungen auffassen. Die Domäne einer Eigenschaft
E bezeichnen wir mit dom(E).
Beispiel 2.2 [Domäne] Wir wollen hier einige Beispiele für Domänen angeben:
• dom(Name):
Alle Folgen von maximal 20 Buchstaben, wobei der erste Buchstabe gross, die anderen
klein geschrieben sind.
• dom(Gewicht):
0 ≤ Gewicht ≤ 150
• dom(Sprache):
{Deutsch, Französisch, Englisch}
2.3.3
Entitätsattribut
Wir erinnern uns, dass ein Faktum die Behauptung darstellt, dass eine Entität für eine Eigenschaft einen bestimmten Eigenschaftswert aufweist. Wir übertragen nun diesen Begriff auf
alle Elemente einer Entitätsmenge
Definition 2.10 [Entitätsattribut] Ein Entitätsattribut assoziert die Entitäten
einer Entitätsmenge mit Eigenschaftswerten, die einer (oder mehreren) Domäne(n)
angehören.
Im folgenden wird gezeigt, dass einem Entitätsattribut verschiedene Kardinalitäten zugrunde
liegen können. Wir unterscheiden in unserem Datenmodell vier solche Kardinalitäten:
• Einfache (Typ 1) Kardinalität
• Konditionelle (Typ C) Kardinalität
• Komplexe (Typ M) Kardinalität
• Komplex-Konditionelle (Typ MC) Kardinalität
Diese vier Typen werden im folgenden vorgestellt.
2-6
2.3.3.1
Einfache oder Typ 1 Kardinalität
Eine Person weist (normalerweise) zu jedem Zeitpunkt genau einen Namen auf. In diesem Fall
liegt eine einfache (oder Typ 1 ) Kardinalität von der Menge Person zur Menge dom(Name)
vor.
Definition 2.11 [Einfache Kardinalität] Eine einfache (Typ 1) Kardinalität N
von einer Menge A zu einer Menge B bedeutet, dass jedes Element in A jederzeit
mit einem Element in B in Beziehung steht. In der Mathematik nennt man eine
solche Kardinalität eine Funktion.
Formal schreiben wir:
N : A 7→ B
Also in unserem Beispiel könnte man
Name: Person 7→ dom(Name)
schreiben.
2.3.3.2
Konditionelle oder Typ C Kardinalität
In unserem Beispiel soll jeder Patient Mitglied höchstens einer Krankenkasse sein, möglicherweise aber auch nicht versichert sein. In diesem Fall liegt eine konditionelle (oder Typ C )
Kardinalität von der Menge Patient zur Menge dom(Krankenkasse) vor.
Definition 2.12 [konditionelle Kardinalität] Eine konditionelle (Typ C) Kardinalität N von einer Menge A zu einer Menge B bedeutet, dass jedes Element
in A höchstens mit einem, möglicherweise mit keinem Element in B in Beziehung
steht. In der Mathematik nennt man eine solche Kardinalität eine partielle Funktion.
Formal schreiben wir:
N : A ֒→ B
In unserem Beispiel würden wir also
Krankenkasse: Patient ֒→ dom(Krankenkasse)
schreiben.
2.3.3.3
Komplexe oder Typ M Kardinalität
Eine Person kennt mindestens ihre Muttersprache. Möglicherweise hat diese Person aber auch
Kenntnisse in mehreren anderen Sprachen. In diesem Fall liegt eine komplexe (oder Typ M )
Kardinalität von der Menge Person in die Menge dom(Sprache) vor.
2-7
Definition 2.13 [komplexe Kardinalität] Eine komplexe (Typ M) Kardinalität N von einer Menge A zu einer Menge B bedeutet, dass jedes Element in A
mindestens mit einem, möglicherweise mit mehreren Elementen in B in Beziehung
steht. Mathematisch gesehen ist das eine Funktion von der Menge A in die Potenzmenge von B (ohne leere Menge).
Formal schreiben wir:
N : A 7→ P(B) \ {}
In unserem Beispiel würden wir also
Sprachen: Person 7→ P(dom(Sprache))\{}
schreiben.
2.3.3.4
Komplex-konditionelle oder Typ MC Kardinalität
In unserem Beispiel kann ein Arzt kein, ein oder mehrere Spezialgebiete haben. In diesem
Fall liegt eine komplex-konditionelle (oder Typ MC ) Kardinalität von der Menge Arzt in die
Menge Spezialgebiet vor.
Definition 2.14 [komplex-konditionelle Kardinalität] Eine komplex-konditionelle
(Typ MC) Kardinalität N von einer Menge A zu einer Menge B bedeutet, dass
jedes Element in A mit beliebig vielen (also auch null oder nur einem) Elementen
in B in Beziehung stehen kann. Mathematisch gesehen ist das eine Funktion von
der Menge A in die Potenzmenge von B.
Formal schreiben wir:
N : A 7→ P(B)
In unserem Beispiel würden wir also
Spezialgebiet: Arzt 7→ P(dom(Spezialgebiet))
schreiben.
Bemerkung 2.3 [Kardinalität und Beziehungsmengen] Dem Begriff Kardinalität werden wir im Abschnitt 2.3.6 noch einmal begegenen, wenn es darum geht die Beziehungen
zwischen Entitätsmengen zu charakterisieren.
2.3.4
Entitätsschlüssel
Wir erinnern uns, dass eine Entität nach Definition eindeutig identifizierbar sein muss. Der
Identifikator einer Entität nennt man Entitätsschlüssel.
Definition 2.15 [Entitätsschlüssel] Ein Entitätsschlüssel ist ein Entitätsattribut, mit dessen Werten die Entitäten einer Entitätsmenge eindeutig zu identifizieren sind.
2-8
Weil mit natürlichen Attributen wie Name oder Wohnort usw. in der Regel keine eindeutige
Identifikationen zu erzielen ist, legt man einem Entitätsschlüssel normalerweise ein künstliches
Attribut wie p_nr (Personalnummer) zugrunde. Dieses Attribut muss gemäss Zehnder [Zeh89]
folgenden Kriterien genügen:
• Der Schlüsselwert ist eindeutig und unveränderlich.
• Eine neuauftretende Entität erhält ihren Schlüsselwert sofort.
Bemerkung 2.4 [Zusammengesetzter Schlüssel] Der Schlüssel kann im Prinzip auch aus
mehreren Attributen bestehen. In diesem Fall spricht man auch von einem zusammengesetzten
Schlüssel.
2.3.5
Darstellung von Entitätsmengen und Entitätsattribute
Entitätsmengen werden in unserem graphischen Modell mit dem Klassensymbol (Rechteck)
dargestellt. Das Rechteck wird mit dem Namen der Entitätsmenge beschriftet. Zusätzlich wird
mit dem Stereotyp <<entity>> angegeben, dass es sich um eine persitente Entitätsmenge
und nicht um eine transiente Klasse handelt.
Entitätsattribute können direkt innerhalb des Rechtecks für die Entitätsmenge angegeben
werden.
Der Entitätsschlüssel wird mittels einer Constraint angegeben. Die graphische Darstellung ist
in der Abbildung 2-2 angegeben.
<<entity>>
Patient
p_nr
name
alter
krankenkasse [0..1]
sprachkenntnisse [1..*]
allergie [0..*]
{Entitykey}
Abbildung 2-2: Darstellung von Entitätsmengen und Attributen
Die Spezifikation für die Attribute ist folgendermassen gegeben:
Attribut
::= name [: type-expression] [multiplicity]
name
::= Name des Attributs
type-expression := Ein primitiver UML Datentyp (Integer, Boolean oder String) oder ein
“Datenbanktyp”. Letztere sind natürlich von der gewählten Datenbank abhängig.
multiplicity
::= lower-bound..upper-bound. lower und upper-bound sind ganze Zahlen
und geben an wieviele Elemente minimal und maximal zugelassen
werden. upper-bound kann durch * ersetzt werden. Dies bedeutet beliebig viele. Falls keine multiplicity angegeben ist, wird 1..1 angenommen.
Beispiele:
name
geburtsDatum : date
sprache [0..*] : string
Nur der Name des Attributs
Mit Datentyp
Mehrwertigkeit
Die Kardinalitäten 1, C, M und MC können folgendermassen dargestellt werden:
2-9
name
krankenkasse[0..1]
sprachkenntnisse[1..*]
allergie[0..*]
2.3.6
Kardinalitätstyp
Kardinalitätstyp
Kardinalitätstyp
Kardinalitätstyp
1
C
M
MC
Beziehungsmengen
Als erstes wollen wir den Beziehungstyp definieren und mit Hilfe dieses Begriffs die Beziehungsmenge.
Definition 2.16 [Beziehungstyp] Beziehungen, an denen jeweils Entitäten der
gleichen Entitätsmengen beteiligt sind, sind vom gleichen Beziehungstyp, sofern
sie allesamt ein und dieselbe Beziehungsart betreffen.
Wir wollen noch an Hand von zwei Beispielen zeigen, was unter einer Beziehungsart gemeint
ist:
1. Welche Ärzte behandeln welchen Patienten?
2. Welche Studenten besuchen welche Vorlesungen?
Wir können nun den Begriff der Beziehungsmenge definieren.
Definition 2.17 [Beziehungsmenge] Eine Beziehungsmenge ist eine eindeutig
benannte Kollektion von Beziehungselementen gleichen Beziehungstyps.
Beispiel 2.3 [Arzt behandelt Patient] In unserem Arzt-Patienten-Beispiel können wir
alle Beziehungen der Art “Arzt behandelt Patient” in einer Beziehungsmenge behandelt
zusammenfassen. Diese Menge zeigt, welcher Arzt welchen Patienten behandelt. Umgekehrt
ist natürlich auch festgehalten von welchen Ärzten ein Patient behandelt wird.
2.3.6.1
Kardinalität
Wie im obigen Beispiel gesehen bestehen zwischen Entitätsmengen und Beziehungsmengen
auch Kardinalitäten. Zwischen der Entitätsmenge Arzt und der Beziehungsmenge behandelt
besteht eine Typ M Beziehung. Im Prinzip sind dieselben Kardinalitätstypen zwischen Entitätsmengen und Beziehungsmengen möglich wie zwischen Entitätsmengen und den Domänen von Attributen Um die Kardinalität darzustellen verwenden wir die sogenannte MCDarstellung. Wir können in unserem Beispiel die Beziehung zwischen Arzt un Patient folgendermassen aufschreiben:
behandelt: Arzt
1:M Patient
Das heisst, von links nach rechts gelesen:
Ein Arzt behandelt ein oder mehrere Patienten (Typ M)
oder von rechts nach links:
Ein Patient wird von genau einem Arzt behandelt (Typ 1).
2-10
2.3.6.2
Beziehungsmenge als Entitätsmenge
Beziehungsmengen können auch als spezielle Entitäten aufgefasst werden. Dies hat den Vorteil,
dass Beziehungsmengen von Entitäten überlagert werden können. Ferner wird es auch möglich,
dass eine Beziehungsmenge Beziehungsattribute besitzt (siehe 2.3).
2.3.6.3
Rekursive Beziehungen
Ein anderer Fall, wo Beziehungsmengen wichtig sind, ist wenn nur eine Entitätsmenge beteiligt
ist. In diesem Fall entstehen rekursive Strukturen. Im folgenden Beispiel wollen wir das Stücklistenproblem behandeln.
Beispiel 2.4 [Stückliste] Gegeben sei eine Entitätsmenge Produkt, die die Produkte (Entitäten) p1 , p2 , p3 , . . . enthalte. In der Regel setzt sich ein einzelnes Produkt aus mehreren anderweitigen Produkten zusammen. Man nennt eine Operation, welche die für die Herstellung
eines Produktes erforderlichen Komponenten bestimmt, eine Auflösung. Offensichtlich stehen die Entitäten der Entitätsmenge Produkt aufgrund einer Auflösungsbeziehung komplexkonditionell (Typ MC) mit Entitäten der gleichen Entitätsmenge in Beziehung. Um dies
darzustellen führen wir die Beziehungsmenge verwendet ein und schreiben:
verwendet: Produkt
MC:MC Produkt
Gelesen:
Ein Produkt verwendet kein, ein oder mehrere weitere Produkte.
Ein Verwendungsnachweis ist eine Operation, die es erlaubt, jene Produkte zu finden, deren
Herstellung eine bestimmte Komponente benötigen. Diese Beziehung ist aber gerade die zur
Auflösungsbeziehung inverse Beziehung. Das heisst, wir können dieselbe Beziehungsmenge
verwendet benutzen und einfach von rechts nach links lesen:
Ein Produkt wird in keinem, einem oder mehreren Produkten verwendet
Die Beziehungselemente der Menge verwendet bestehen aus geordneen Paaren. Das erste
Element ist das herzustellende Produkt, das zweite Element ist ein für die Herstellung erforderliches Produkt.
Wir nehmen an, dass die Herstellung des Produktes p1 die Produkte p2 , p3 und p4 benötigt.
In diesem Fall werden die Beziehungselemente
< p1 , p 2 >
< p1 , p 3 >
< p1 , p 4 >
zur Beziehungsmenge verwendet gehören.
2-11
2.3.7
Darstellung von Beziehungsmengen
In unserem Modell werden Beziehungsmengen als ausgezogene Linien dargestellt. Die Kardinalitäten werden mit der m . . . n Notation angegeben
Mit Hilfe von Beziehungsmengen können alle Beziehungsarten (1:1, C:1, M:1 usw.) zwischen
Entitäten dargestellt werden. In der Abbildung 2-3 ist eine M zu M (a) eine 1 zu M (b) und eine
1 zu MC Beziehung zwischen Ärzten und Patienten dargestellt. Alle anderen Beziehungsarten
werden analog dargestellt.
<<entity>>
Arzt
1..*
behandelt
1..*
<<entity>>
Patient
a) Ein Arzt behandelt mehrere Patienten und ein Patient wird von mehreren Aerzten behandelt.
<<entity>>
Arzt
1
behandelt
1..*
<<entity>>
Patient
b) Ein Arzt behandelt mehrere Patienten und ein Patient wird von genau einem Arzt behandelt.
<<entity>>
Arzt
1
behandelt
0..*
<<entity>>
Patient
c) Ein Arzt behandelt kein, ein oder mehrere Patienten, ein Patient wird von einem
Arzt behandelt.
Abbildung 2-3: Darstellung von Beziehungsmengen (M:M und M:1)
Bemerkung 2.5 [Lesen der Beziehung] Der ausgefüllte Pfeil beim Namen der Beziehungsmenge gibt an, in welcher Richtung gelesen werden muss. In userem Fall also “Arzt behandelt
Patient”.
2.3.8
Beziehungsattribut
Das Prinzip eines Beziehungsattributes ist mit jenem eines Entitätsattributes vergleichbar.
Wir erinnern uns, dass eine Beziehungsmenge auch als spezielle (abhängige) Entitätsmenge
angesehen werden kann.
Definition 2.18 [Beziehungsattribut] Ein Beziehungsattribut assoziert die Beziehungselemente einer Beziehungsmenge mit Eigenschaftswerten, die einer (oder mehreren)
Domäne(n) angehören.
Bemerkung 2.6 [Kardinalität von Beziehungsattributen] Für Beziehungsattribute gelten dieselben Kardinalitäten wie für Entitätsattribute. Das heisst, einfache (Typ 1), konditionelle (Typ C) und komplexe (Typ M) Kardinalität.
Beispiel 2.5 [Anteil in Stückliste] Als Beispiel betrachten wir wieder das Stücklistenproblem. Wir wollen in unserem Modell nun eine weitere Information einfügen, die angibt, wieviele
Komponenten eines Typs zur Herstellung eines bestimmten Produktes nötig sind.
Es ist sofort ersichtlich, dass dies nicht eine Eigenschaft des Produktes ist, sondern die Eigenschaft einer Beziehung zwischen zwei Produkten. Wir führen daher das Beziehungsattribut
Anzahl mit der Domäne Ganze-Zahl ein.
2-12
2.3.9
Darstellung von Beziehungsattributen
Falls vorhanden werden Beziehungsattribute, wie in der Abbildung 2-4 angegeben als Entitätsmengen dargestellt.
<<entity>>
Arzt
1..*
behandelt
1..*
<<entity>>
Patient
<<entity>>
ArztPatient
Diagnose
Abbildung 2-4: Beziehungsattribut
2.4
Semantische Datenmodellierung
Das ER-Modell nach Chen wurde von verschiedenen Autoren zur semantischen Datenmodellierung erweitert. Wichtige zusätzliche Konzepte sind die Aggregation und die Generalisierungshierarchie.
2.4.1
Generalisierung
Wir haben im Abschnitt 2.3.1 an Hand des Arzt-Patienten-Beispiels gesehen, wie mit Hilfe
der Überlagerung Redundanz vermieden werden kann. Mit der Überlagerung von Entitätsmengen können wir das Konzept der stufenweise Spezialisierung und Generalisierung von
Informationen realisieren.
Beispiel 2.6 [Person als Generalisierung] Im Arzt-Patienten-Beispiel ist die Entitätsmenge Person eine Generalisierung von Arzt und Patient. Umgekehrt sind Patient und
Arzt Spezialisierungen von Person.
Mit der Generalisierung und der Spezialisierung können wir unter den Entitätsmengen eine
Hierarchie einführen. Wir können dann von Vorgängern und Nachfolgern einer Entitätsmenge
sprechen. Im objektorientierten Ansatz spricht man von einer Is-a-Hierarchie (ist ein). Zum
Beispiel kann man sagen: Ein Arzt ist eine Person. Person ist in diesem Fall die allgemeinere
Entität (Generalisierung), der Arzt die speziellere Entität (Spezialisierung). Ferner ist klar,
dass ein Arzt nur existieren kann, wenn er als Person existiert. Daher ist Person eine Kernentität und Arzt eine abhängige Entität.
2.4.1.1
Möglichkeiten bei der Generalisierung
Bei der Generalisierung zweier Entitätsmengen zu einer neuen Entitätsmenge können vier
verschiedene Fälle resultieren. Diese Fälle wollen wir nachfolgend an Hand von Beispielen
betrachten.
1. Eine Person ist entweder eine Frau oder ein Mann. Frau und Mann sind disjunkt und
überlagern Person vollständig.
2-13
2. Die Mengen der Krankenzimmer und der Operationssäle sind disjunkt, aber es gibt im
Spital noch weitere Räume wie Untersuchungszimmer, Büros usw.
3. In einem Spital gehört jede relevante Person entweder zum Personal oder ist ein Patient.
Wie wir aber schon gesehen haben kann ein Angestellter auch gleichzeitig Patient sein.
4. Dass ein Patient auch Arzt sein kann, haben wir schon gesehen. In einem Spital kann
es aber auch weitere Personen wie Krankenschwestern, Reinigungspersonal usw. geben.
2.4.2
Darstellung von Generalisierungen
Die Genaralisierung wird mit Hilfe eines Pfeiles dargestellt Die verschiedenen Möglichkeiten, die im Abschnitt 2.3 gezeigt wurden, werden mit Hilfe von abstrakten Klassen und mit
speziellen Constraints (overlapping oder disjunkt) dargestellt. Die graphischen Darstellungen
für diese Fälle sind in der Abbildung 2-5 angegeben.
<<entity>>
Person
<<entity>>
Raum
{disjoint}
{disjoint}
<<entity>>
Frau
<<entity>>
Mann
a) Frau und Mann sind disjunkt und ueberlagern
Person vollstaendig.
<<entity>>
Operationssaal
b) Operationssaal und Krankenzimmer sind disjunkt
ueberlagern aber Raum nicht vollstaendig.
<<entity>>
Person
<<entity>>
Person
{overlapping}
<<entity>>
Angestellter
<<entity>>
Krankenzimmer
{overlapping}
<<entity>>
Patient
c) Angestellter und Patient sind nicht disjunkt
und ueberlagern Person vollstaendig
<<entity>>
Arzt
<<entity>>
Patient
c) Angestellter und Patient sind nicht disjunkt
ueberlagern Person aber nicht vollstaendig
Abbildung 2-5: Darstellung der verschiedenen Überlagerungen
2-14
2.4.3
Aggregation
Werden mehrere Einzelobjekte (z. B. Patient und Spital) zu einem eigenständigen Einzelobjekt (z. B. Belegung) zusammengefasst, dann spricht man von Aggregation. Dabei wird das
übergeordnet eigenständige Ganze (in userem Fall die Belegung) Aggregat genannt. Die Teile
(in unserem Fall Spital und Patient), aus denen es sich zusammensetzt, heissen Komponenten.
Aggregat und Komponenten werden als Entitätsmengen deklariert.
Bei Aggregation/Zerlegung wird zwischen Rollen- und Mengenaggregation unterschieden.
Eine Rollenaggregation liegt vor, wenn es mehrere rollenspezifische Komponenten gibt und
diese zu einem Aggregat zusammengefasst werden.
Beispiel 2.7 [Operaqtionsteam] Ein Operationsteam besteht aus Chirugen, einem Anästhesisten und Operationsschwestern. Das Operationsteam ist das Aggregat, die Personen sind
die Komponenten, wobei die Personen in verschiedenen Rollen in Erscheinung treten.
Eine Mengenaggregation liegt vor, wenn das Aggregat durch Zusammenfassung von Einzelobjekten aus genau einer Entitätsmenge entsteht.
Beispiel 2.8 [Fussballmanschaft] Als Beispiel können wir eine Fussballmanschaft betrachten, die eben aus Fussballspieler besteht.
2.4.4
Darstellung von Aggregationen
Die Agreggation wird als Beziehungen zwischen dem Ganzen und den entsprechenden Teilen
angezeigt. Auf der Seite des Ganzen wird am Anfang der Beziehung noch eine Raute gezeichnet, die angibt, dass es sich um eine Aggregation handelt. In der Abbildung 2-6 ist ein
Operationsteam als Aggregation dargestellt.
besteht aus
1..*
<<entity>>
Chirurg
<<entity>>
Operationsteam
1..*
<<entity>>
OpSchwester
besteht aus
1..*
<<entity>>
Anästhesist
Abbildung 2-6: Darstellung der Aggregation
2.4.5
Vorgehen
In diesem Abschnitt werden die methodischen Konstruktionsschritte zur Entwicklung von
konzeptionellen Datenmodellen dargestellt. Wir werden versuchen, wenn immer möglich, das
Modell Top-Down zu entwickeln. Das heisst, von den Kernentitäten und deren Beziehungen bis
hinunter zu den Attributen. Wir wollen das Vorgehen gerade an Hand des folgenden Beispiels
präsentieren.
Beispiel 2.9 [Kurssystem: Beschreibung]
Gegeben seien die folgenden Realitätsbeobachtungen.
2-15
1. Eine Unternehmung organisiert firmeninterne Kurse unterschiedlichen Typs (z.B. Informatik,
Betriebswirtschaftslehre usw.). Für jeden Kurstyp gibt es normalerweise jährlich mehrere
Kursangebote.
2. Jedes Kursangebot erfordert einen Lehrer. Ein Lehrer ist in der Regel für mehrere Kursangebote zuständig.
3. Für jedes Kursangebot schreiben sich in der Regel mehrere Studenten ein. Ein Student
kann sich für mehrere Kursangebote einschreiben.
4. Die Dozenten und Studenten sind alle Angestellte der Firma. Ein Angestellter kann
sowohl als Dozent wie auch als Student tätig sein.
5. Jeder Kurs erfordert einen Klassenraum. Ein Klassenraum kann von verschiedenen
Kursen belegt sein, falls diese nicht gleichzeitig stattfinden.
6. Kurse müssen in einer vorgegebenen Sequenz besucht werden. In der Regel können einem
bestimmten Kurstyp mehrere anderweitige Kurstypen folgen. Umgekehrt erfordert ein
bestimmter Kurstyp in der Regel vorgängig den Besuch von mehreren anderweitigen
Kurstypen.
2.4.6
Erkennen von Entitätsmengen
Die für die Aufgabe wesentlichen Entitätsmengen der Realität werden aufgrund einer reifenden
Vorstellung des Entwicklers vom Problemraum deduktiv erkannt. Dieses ist ein grosser Schritt,
der nur mit einiger Erfahrung im Anwendungsbereich und mit der aktiven Hilfe des Anwenders
zuverlässig funktionieren kann. Die folgenden Ansatzpunkte für diese Aufgabe stammen aus
[CY91].
Wo sollte man suchen?
• Im Problemraum, in textlichen und in graphischen Darstellungen.
• Im Gespräch mit den Anwendern des Systems.
Wonach sollte man suchen?
• Struktur der Aufgabe,
• andere Systeme, über die Informationen gespeichert werden müssen,
• Ereignisse, an die man sich erinnern muss,
• Rollen, die von Individuen gespielt werden,
• Orte, an denen für das System Wichtiges passiert,
• Organisationseinheiten, denen Menschen angehören, oder die sonst wichtig sind.
Was ist zu berücksichtigen?
• Braucht das System die Erinnerung an frühere Begebenheiten?
2-16
• Muss das System auf Anforderungen von aussen Leistungen erbringen?
• Gibt es in der Entitätsmenge mehr als eine Entität?
• Gibt es mehr als ein Attribut?
• Gibt es in den Beschreibungen Synonyme und Homonyme?
Welche Fehler kann man machen?
• Erinnerung speichern, die eigentlich nicht benötigt wird.
• Aufgaben oder Leistungen berücksichtigen, die niemals abgefordert werden.
• Entitätsmengen modellieren, die nur aus einem Element bestehen.
• Ergebnisse speichern, die aus anderen abgeleitet werden können.
Aufgrund der Betrachtungen der Realität wird ein Entitäts-Kandidaten-Katalog aufgestellt.
Pro Entitäts-Kandidat werden der Name der Entitätsmenge, falls nötig eine charakterisierende
Beschreibung und eventuell typische Beispiele festgehalten.
Beispiel 2.10 [Kurssystem: Mögliche Entitätsmengen des Systems] In unserer Beschreibung sind alle Substantive unterstrichen. Diese kommen als Kanditaten für Entitätsmengen
in frage.
Unternehmung, Kurs, Typ, Informatik, Betriebswirtschaftslehre, Kurstyp, Kursangebot, Lehrer, Student, Dozent, Angestellter, Firma, Klassenraum, Sequenz,
Besuch.
Als nächsten Schritt werden falls vorhanden Synonyme und Homonyme aus dem erstellten
Katalog entfernt. Entwickler und Anwender müssen sich auf einen Begriff einigen. Diese Abmachungen sind dann für das ganze Projekt verbindlich. Nachfolgend noch die Definition von
Synonymen und Homonymen.
Definition 2.19 [Synonym] Als Synonyme bezeichnet man zwei Wörter derselben Sprache, welche dieselbe (oder fast dieselbe) Bedeutung haben. Zum Beispiel
sind Samstag und Sonnabend Synonyme.
Definition 2.20 [Homonym] Als Homonym bezeichnet man Wörter, die verschiedene Bedeutungen haben können. Zum Beispiel bezeichnet das Wort Tau
sowohl eine Form von Niederschlag, ein Seil sowie der griechische Buchstabe τ .
Beispiel 2.11 [Kurssystem: Synonyme und Homonyme] In unserem Beispiel können die
folgenden Synonyme gefunden werden. Die für den weiteren Verlauf des Entwurfs festgelegten
Begriffe sind fett gedruckt.
• Unternehmung – Firma
• Kurs – Kurstyp – Typ
• Lehrer – Dozent
2-17
• Kurs – Kursangebot
Bemerkung 2.7 [Kurstyp und Kursangebot] Kurstyp und Kursangebot sind in dieser
Lösung keine Synonyme. Kurstyp ist die Beschreibung eines Kurses (Inhalt, Voraussetzungen,
Literatur usw.). Kursangebot hingegen bezeichnet die konkrete Durchführung eines Kures von
einem bestimmten Typ.
Im Kontext der Aufgabe wird Kurs sowohl für Kurstyp wie auch für Kursangebot verwendet
und ist daher ein Homonym.
• Im Punkt 5 der Aufgabe wird Kurs für Kursangebot verwendet.
Jeder Kurs erfordert einen Klassenraum.
• Im Punkt 6 der Aufgabe wird Kurs für Kurstyp verwendet.
Kurse müssen in einer vorgegebenen Sequenz besucht werden.
Nun muss entschieden werden, welche Entitäts-Kandidaten auch wirklich als Entitätsmengen
aufgenommen werden. Entitäts-Kandidaten sind wahrscheinlich Entitätsmengen, wenn sie:
• Eine eigene Bedeutung haben, die das System unabhängig von seiner Implementierung
beachten muss.
• Eigene Attribute haben. Wobei zu beachten ist, dass beim Modellieren einer Datenarchitektur die genauen Attribute zu diesem Zeitpunkt meistens noch nicht bekannt sind.
• In das abgegrenzte Gebiet hineingehören.
Entitäts-Kandidaten sind sicher keine Entitätsmengen, wenn sie
• Berichte, Auswertungen oder Auskünfte sind. Diese sind Ergebnis von Funktionen und
deshalb keine Entitätsmengen.
• Nur dem Inhalt nach Bedeutung haben, jedoch nicht als eigene Objekte (zum Beispiel
Beziehungen, Aktionen oder Wertebeispiele für Attribute).
Beispiel 2.12 [Kurssystem: Keine Entitätsmengen]
Firma
Besuch
Informatik
Betriebswirtschaftslehre
Sequenz
Zur Firma werden keine Daten gesammelt daher muss diese
auch nicht als Entitätsmenge aufgenommen werden. Die Firma
kann auch als das gesammte System angesehen werden.
Drückt eine Beziehung zwischen Kursangebot und Student aus.
Diese beiden Begriffe sind Eigenschaftswerte des Kurstyps und
keine eigenen Entitäten.
Sequenz ist eine Beziehung zwischen Kurstypen und keine Entitätsmenge.
2-18
Unter den gefundenen Entitätsmengen werden noch die Kernentitätsmengen gesucht. Dabei
können die folgenden Kriterien angewandt werden.
• Sind die Kernentitäten nicht Speziallfälle einer generelleren Entitätsmenge?
• Sind die Kernentitätsmengen alle paarweise disjunkt?
• Sind die Kernentitäten nicht Bestandteil von Entitäten in einer anderen Entitätsmenge?
Beispiel 2.13 [Kurssystem: Entitäten des Systems] Wir können nun den bereinigten
Katalog der Entitätsmengen angeben und gleichzeitig die Kernentitäten bezeichnen.
2.4.7
Entität
Angestellter
Dozent
Student
Klassenraum
Kurstyp
Kern
Ja
Nein
Nein
Ja
Ja
Kursangebot
Nein
Begründung
Existiert unabhängig vom System.
Muss ein Angestellter sein.
Muss ein Angestellter sein.
Existiert physisch und ist unabhängig vom System.
Kann unabhängig von allen andern Entitäten des Systems definiert werden.
Kann nur zu einem Kurstyp existieren.
Erkennen von Beziehungsmengen
Alle Entitätsmengen werden nun paarweise gegenübergestellt und nach in der Realität existierenden Verknüpfungen untersucht. Auf diese Weise werden logische Abhängigkeiten erkannt. Für jeden erkannten Beziehungstyp wird festgehalten:
• Name der Beziehung.
• Die beteiligten Entitäten und die Kardinalitäten.
Beispiel 2.14 [Kurssystem: Erkennen von Beziehungsmengen] Aus der Realitätsbeschreibung können wir folgende Beziehungen zwischen Entitäten feststellen:
Aus Punkt 1
hat Typ: Kursangebot
Aus Punkt 2
doziert: Dozent
Aus Punkt 3
besucht: Student
1:MC
MC:1 Kurstyp
Kursangebot
MC:MC Kursangebot
Aus Punkt 4
ist: Dozent C:1 Angestellter
ist: Student C:1 Angestellter
Aus Punkt 5
braucht: Kursangebot
MC:1 Klassenraum
2-19
Aus Punkt 6
folgt: Kurstyp
2.4.8
MC:MC Kurstyp
Semantische Datenmodellierung
Aus den gefundenen Beziehungen, suchen wir nun nach möglichen Genralisierungen (bzw.
Spezialisierungen) sowie nach Aggregationen.
Beispiel 2.15 [Kurssystem: Generalisierung] Aus den Beziehungen zwischen Angestellter,
Dozent und Student sieht man, dass Dozent und Student Spezialisierungen von Angestellter
darstellen. Dozent und Student sind nicht disjunkt und überdecken Angestellter nicht vollständig.
2.4.9
Erkennen von Attributen
Einige Entitätsattribute sind bereits bekannt, wenn die entsprechende Entitätsmenge identifiziert wird. In Rücksprachen mit dem Anwender und nach Auswertung der vom Anwender
benutzten Formblätter, Karteikarten usw. werden weitere Attribute identifiziert. Auch hier
ist die Erfahrung des Entwicklers gefordert. Folgende Punkte sind zu beachten:
• Jedes Attribut muss einen eindeutigen Namen besitzen. Der Wertebereich und die Kardinalität der Attribute müssen zweifelsfrei festgelegt werden.
• Attribute aus Vorgängersystemen dürfen nicht einfach unkritisch in das neue System
übernommen werden. Man findet häufig Informationen, die gespeichert, aber nirgends
benutzt werden.
Bemerkung 2.8 [Definition der Attribute] Attribute können auch später bei der Entwicklung der Funktionen des Systems identifiziert werden. Ist die Datenarchitektur gut, so sollte
es kein Problem darstellen, solche Attribute bei der richtigen Entitätsmenge einzuordnen.
Beispiel 2.16 [Kurssystem: Erkennen von Attributen] Aus der Beschreibung des Systems lassen sich nicht viele Attribute eindeutig bestimmen. Nachfolgend eine (unvollständiger)
Liste.
Angestellter
a_nr, name
Klassenraum
kr_nr, plaetze
Kurstyp
kt_nr, bezeichnung
Kursangebot
ka_nr, kurstag[1..*] : Date
2-20
2.4.10
Zeichnen des ERDs
Nun sollten alle Informationen vorhanden sein, um ein korrektes ERD zu zeichnen. Es gilt
zu beachten, dass dabei keine neuen Informationen eingführt werden. Es ist nur eine andere
Darstellungsart für das bisher entwickelte Modell.
Beispiel 2.17 [Kurssystem: Zeichnen des ERDs] Das resultierende ERD ist in der Abbildung 2-7 dargestellt.
<<entity>
Angestellter
a_nr
name
0..*
<<entity>
setzt voraus
Kurstyp
kt_nr
bezeichnung
0..*
{overlapping}
<<entity>
Student
1
<<entity>
Dozent
doziert
1
<<entity>
Klassenraum
kr_nr
plaetze
hat Typ
0..*
besucht
0..*
<<entity>
0..*
Kursangebot
0..*
ka_nr
kurstag[1..*] : Date 0..*
1
braucht
Abbildung 2-7: ERD zum Kursproblem
2-21
2.5
2.5.1
Übungen
Fragen zur Theorie
Aufgabe 2.1 [Entität Eigenschaft] Wie kann entschieden werden, ob ein Begriff des gegebenen Problemraumes als Entität oder als Eigenschaft betrachtet werden muss?
Geben Sie Beispiele an.
Aufgabe 2.2 [Domäne] Erklären Sie an Hand eines Beispiels den Begriff “Domäne”. Welches ist die Bedeutung der Domäne im ER-Modell?
Aufgabe 2.3 [Kardinalitäten] Erklären Sie die Bedeutung der verschiedenen Kardinalitäten
zwischen Entitätsmengen an Hand von Beispielen.
Aufgabe 2.4 [Generalisierung] Erklären Sie ganz allgemein, in welchen Fällen die Generalisierung von Entitätsmengen Sinnvoll ist?
Geben Sie ein Beispiel für eine Generalisierung an.
Aufgabe 2.5 [Synonyme] Erklären Sie den Begriff “Synonyme” und suchen Sie einige
Beispiele dazu.
Warum ist es wichtig, dass Synonyme erkannt werden?
2.5.2
Verwalten von Computerliteratur
Aufgabe 2.6 [Erstellen eines ERDs] Eine Unternehmung beschliesst, die Verwaltung der
Computerliteratur zu automatisieren. Es wird festgestellt:
1. Für ein Manual existieren in der Regel mehrere Exemplare mit unterschiedlichen Standorten.
2. Für ein Manual gibt es in der Regel Zusätze (Technical News Letters, Supplements,
usw.), die allesamt am Standort des Manuals vorliegen müssen.
3. Jedes Manual lässt sich einer bestimmten Subjektgruppe zuordnen.
4. Manuals werden samt Zusätzen von Mitarbeitern ausgeliehen oder befinden sich in deren
Besitz (das heisst, der Mitarbeiter muss das Manual nur zurückgeben, wenn er aus der
Firma austritt).
5. Manuals werden samt Zusätzen von Lieferanten geliefert. Allerdings treffen die Zusätze
erst im Verlaufe der Zeit ein. Es ist zu gewährleisten, dass jedes Manual schliesslich alle
erforderlichen Zusätze aufweist.
Aufgaben
a) Suchen Sie alle möglichen Entitätsmengen des Systems und bestimmen Sie Synonyme
und Homonyme.
b) Entscheiden Sie, welche der möglichen Entitätsmengen im System aufgenommen werden.
Bestimmen Sie gleichzeitig die Kernentitätsmengen des Systems.
c) Zählen Sie alle wichtigen Beziehungen zwischen Entitätsmengen des Systems auf.
d) Überlegen Sie, ob Spezialisierungen oder Generalisierungen ausgemacht werden können
und bestimmen Sie die notwendigen Beziehungsmengen.
2-22
e) Zeichnen Sie die globale Datenarchitektur des Systems.
f) Bestimmen Sie, soweit dies aus der Beschreibung hervorgeht, die Entitäts- und Beziehungsattribute. Dabei gilt es zu beachten: gilt es zu beachten:
• Vollständigkeit (alle Zusätze vorhanden) und Standort der Manuals müssen überprüft werden können.
• Die Ausleihe der Manuals muss überprüft werden.
• Es sind diverse Statistiken zu erstellen (Manualliste nach Subjektgruppe, Ausleihen,
usw.).
2-23
Kapitel 3
Das Relationenmodell
In diesem Abschnitt werden die Konzepte einer relationalen Datenbank vorgestellt. Das Relationenmodell ist eine der Möglichkeiten, die im konzeptionellen Datenmodell (siehe Kapitel 2)
festgehaltenen Informationen im Computer abzubilden. Weitere Möglichkeiten, das konzeptionelle Datenmodell Abzubilden sind: Das hierarchische, das Netzwerk und das objektorientierte Modell.
Die Abbildung des konzeptionellen Modells in den Computer (logische Ebene) muss den folgenden Kriterien genügen:
• Die Abbildung muss alle im konzeptionellen Entwurf enthaltenen Informationen erhalten. Dies ist nur informal möglich, da die Werkzeuge zur Erstellung des konzeptionellen
Modells (ERD siehe Kapitel 2) mächtiger sind als das relationale Modell. Die Erhaltung
der Informationen muss durch Applikationsprogramme unterstützt werden.
• Die Darstellung der Daten muss redundanzfrei sein.
• Die Darstellung der Daten muss widerspruchsfrei sein.
Die Darstellung der Daten im Computer wird ganz allgemein als logisches Datenmodell bezeichnet.
Das relationale Datenmodell wurde 1970 von Codd in [Cod70] definiert. Im Gegensatz zu
anderen Datenmodellen (z.B. objektorientierte Modelle) ist die Definition von Codd weltweit
akzeptiert.
3.1
Attribute und Domänen
Definition 3.1 [Attribute und Domänen] Sei U eine nichtleere, endliche
Menge, das Universum. Ein Element A ∈ U heisst Attribut.
Sei D = {D1 , . . . , Dm } eine endliche Menge endlicher nichtleerer Mengen. Jedes
Di wird Domäne oder Wertebereich genannt. Ferner existiert eine Funktion dom :
U 7→ D. dom(A) heisst die Domäne von A. Ein w ∈ dom(A) wird Attributwert
für A genannt.
Ein Attribut kann man sich als eine Eigenschaft eines Objektes vorstellen. Die Menge U
enthält daher die Namen aller für das System wichtigen Eigenschaften.
Beispiel 3.1 [Universum] Im beispiel im Anhang A) besteht das Universum aus den folgenden Elementen:
3-1
U = {l_nr, name, ort, plz, b_nr, bestelldatum, lieferdatum, p_nr,
bezeichnung, jahrgang, liefereinheit, lagermenge, min_lagermenge, menge}
Die Domäne eines Attributs ist die Menge der möglichen Eigenschaftswerte dieses Attributs.
Bemerkung 3.1 [Attribut und Eigenschaft] Achtung: Der Begriff “Attribut” im relationalen Modell entspricht nicht dem Begriff “Entitätsattribut” im ER-Modell sondern dem
Begriff “Eigenschaft” (siehe Kapitel 2).
Bemerkung 3.2 [Atomare Attribute] Im Relationenmodell ist die Domäne eines Attributs
abstrakt (oder atomar). Das heisst, vom Modell her gesehen, haben die Werte einer Domäne
keine semantische Bedeutung (die Attributwerte sind im Modell uninterpretiert). Das Modell
kennt jedoch einige Operationen, die auf den Domänen definiert sind. Dies ist im wesentlichen
der Gleichheitstest.
Ist auf einer Domäne eine Ordnungsrelation definiert so sind auch die Operationen >, ≥, <
und ≤ dem Datenbanksystem bekannt.
Insbesondere können die Werte einer Domäne nicht mengen- oder relationenwertig sein (siehe
Abschnitt 3.2).
Beispiel 3.2 [Attribute und Domänen] In der folgenden Tabelle sind Beispiele für Attribute und ihre Domänen angegeben.
Attribute und Domänen
Attribut
Domäne
Name
Zeichenkette der Länge ≤ 30
Ort
Zeichenketten der Länge ≤ 25
Liefereinheit Zahlen zwischen 1 und 120
Bestelldatum Datum
Bemerkung 3.3 [Implementation von Domänen] In den meisten relationalen Datenbanksystemen sind die Domänen nicht implementiert. Es werden dem Benutzer nur fix vorgegebene
Datentypen wie INTEGER, FLOAT, CHAR, DATE, . . . zur Verfügung gestellt. Der Benutzer
hat meistens keine Möglichkeit weitere Domänen zu definieren.
In Informationssystemen kommt es sehr häufig vor, dass der Wert eines Attributs einer Entität
nicht bekannt ist. Trotzdem möchte man die Entität erfassen und das entsprechende Feld leer
lassen. Damit dies möglich ist, werden sogennante Nullwerte eingeführt.
Definition 3.2 [Nullwerte] Ein Nullwert ist ein spezieller Wert, der einfach zu
der Domäne eines Attributs hinzugefügt wird. Die Bedeutung eines Nullwertes ist
“Wert unbekannt”.
Bemerkung 3.4 [Ein Nullwert ist nicht 0] Ein Nullwert ist also nicht einfach die Zahl 0
(oder eine leere Zeichenkette) sondern kann von diesen eindeutig unterschieden werden.
Nullwerte sind sehr umstritten, da deren Bedeutung (Semantik) nicht in jedem Fall klar ist.
Auf die spezielle Problematik von Nullwerten werden wir noch im Kapitel 5 zu sprechen
kommen.
3-2
3.2
Relationenschema, Relation und Tupel
Definition 3.3 [Relationenschema] Eine nicht leere Menge R ⊆ U heisst Relationenschema.
Das Relationenschema ist die Menge aller Eigenschaften einer Entität und entspricht daher
dem Typ der Entität.
Definition 3.4 [Relation und Tupel] Eine Relation r über das Relationenschema R = {A1 , . . . , An } (kurz r(R)) ist eine endliche Menge von Abbildungen
der Form
t : R −→
n
[
dom(Ai ) wobei t|Ai ∈ dom(Ai ) für i = 1, . . . , n
i=1
t|Ai bezeichnet dabei die Einschränkung der Abbildung t auf Ai . Die Abbildungen
t heissen Tupel über R. Die Menge aller Relationen über einem Relationenshema
R wird mit REL(R) := {r|r(R)} bezeichnet.
Für X ⊆ R bezeichnet man die Einschränkung t|X von t auf die Menge X als
X-Wert von t (man schreibt auch t(X)).
Bemerkung 3.5 [Kartesisches Produkt] Sehr oft wird die Relation als Teilmenge des
Kartesischen Produkts der Domänen der Attribute definiert
r(R) ⊆ dom(A1 ) × · · · × dom(An )
Diese Definition ist fragwürdig, da die Reihenfolge der Spalten im Relationenschema fixiert
wird. Die Reihenfolge der Attribute in einem Relationenschema hat aber im relationalen Modell keine Bedeutung.
Von dieser Definition her stammt auch der Ausdruck relationale Datenbank, da in der Mathematik ganz allgemein eine Teilmenge des kartesischen Produkts von Mengen als Relation
bezeichnet wird.
Beispiel 3.3 [Relationen und Tupel] Wir betrachten als Beispiel für die oben definierten
Begriffe die Lieferanten aus dem Beispiel im Anhang A.
• Das Relationenschema für Entitäten des Typs lieferanten.
Lieferanten = {l_nr, name, ort, plz}
• Die Tupel der Relation lieferant. Jedes Tupel repräsentiert eine Entität des Typs
lieferant.
t1 (l_nr) = 1, t1 (name) = ′ Dettwiler′ , t1 (ort) = ′ Bern′ , t1 (plz) = ′ 3016′
t2 (l_nr) = 2, t2 (name) = ′ Haller′ , t2 (ort) = ′ T hun′ , t2 (plz) = ′ 3604′
t3 (l_nr) = 3, t3 (name) = ′ W alter′ , t3 (ort) = ′ T hun′ , t3 (plz) = ′ 3604′
• Die Menge aller gegebenen Tupel bildet eine Relation
r(lieferant) = {t1 , t2 , t3 }
3-3
Relationen werden oft als Tabellen dargestellt. In den Zeilen der Tabelle stehen die Tupel und
in den Spalten die Attributwerte. Diese Darstellungsart ist in vielen Fällen angebracht und wir
werden diese auch verwenden. Die Darstellung einer Relation als Tabelle sugeriert leider, dass
die Reihenfolge der Attribute (Spalten) und die Reihenfolge der Tupel (Zeilen) eine Rolle
spielen könnten. Dies ist aber ganz klar nicht der Fall wie aus den gegebenen Definitionen
herausgeht und dies darf man nicht vergessen. In der Abb. 3-1 ist die Lieferantenrelation aus
dem Beispiel 3.3 in einer Tabelle dargestellt.
String
Numeric
l_nr
Relation
Basel
Bern
usw.
1000−9999
name
ort
plz
1
Dettwiler
Bern
3016
2
Haller
Thun
3604
3
Walter
Thun
3602
Domänen
Relationenschema
Tuppel
.
.
.
Abbildung 3-1: Die Relation Lieferant
Bemerkung 3.6 [Relationen sind Mengen] Da eine Relation als Menge von Tupeln
definiert ist, kann ein Tupel in einer Relation höchstens einmal vorkommen. Dies ist mathematisch klar, da ein Element entweder in der Menge ist oder nicht.
Wir wollen an dieser Stelle noch den Begriff der lokalen Integritätsbedingung einführen.
Definition 3.5 [Lokale Integritätsbedingungen] Sei R ein Relationenschema.
Eine Menge von Abbildungen B = {b|b : REL(R) → {true, f alse}} nennt man
eine Menge lokaler Integritätsbedingungen für das Relationenschema R. R :=
(R, B) heisst erweitertes Relationenschema. Eine Realtion r über R (kurz r(R))
ist eine Relation r über R mit b(r) = true für alle b ∈ B (kurz: B(r) = true).
Die Menge aller Relationen über einem erweiterten Relationenschema R wird mit
SATR (B) := {r|r ∈ r(R)} bezeichnet.
Beispiel 3.4 [Lokale Integritätsbedingung] In der Relation bestellung unseres Beispiels
können wir mit Hilfe einer lokalen Integritätsbedingung verlangen, dass das Bestelldatum
immer kleiner als das Lieferdatum ist, falls das Lieferdatum nicht null ist (das heisst, falls
das Lieferdatum überhaupt schon erfasst ist).
3-4
b(r(bestellung)) =


true







3.3
∀t ∈ r(bestellung) :
t(bestelldatum) < t(lieferdatum)∨
t(lieferdatum) = null
f alse sonst
Semantische Schlüssel
Wir haben gesehen, dass in einer Relation alle Tupel t verschieden sein müssen. Diese Tatsache
führt zu den Begriffen der identifizierenden Attributmenge und des Schlüssels.
Definition 3.6 [Identifizierende Attributmenge] Eine identifizierende Attributmenge für eine Relation r(R) ist eine Menge K := {A1 , . . . , Ak } ⊆ R mit
∀t1 , t2 ∈ r(R)[t1 6= t2 ⇔ ∃Aj ∈ K : t1 (Aj ) 6= t2 (Aj )]
Das heisst nichts anderes, als das jedes Tupel t ∈ r(R) durch die Attributwerte von A1 , . . . , Ak
eindeutig bestimmt ist.
Bemerkung 3.7 [Identifizierende Attributmenge existiert] Aus der Definition der Relationen folgt sofort, dass das Relationenschema R für jede Relation r(R) eine identifizierende
Attributsmenge ist.
Definition 3.7 [Semantischer Schlüssel] Ein semantischer Schlüssel K :=
{A1 , . . . , Ak } ist eine bezüglich ⊆ minimale identifizierende Attributsmenge. Das
heisst, dass wenn aus der Menge K ein Attribut Aj entfernt wird, die resultierende
Menge K \ Aj keine identifizierende Menge mehr ist.
Aus den Definitionen und der Bemerkung 3.7 können wir sofort sehen, dass jede Relation r(R)
mindestens einen Schlüssel haben muss (im schlimmsten Fall alle Attribute).
Beispiel 3.5 [Beispiele für semantische Schlüssel] Wir betrachten nun verschiedene
Relationen über das Relationenschema lieferant.
r1 = {< 1, M eier, 3012, Bern >,
< 1, M eier, 3006, Bern >}
In diesem Beispiel ist {plz} ein Schlüssel. Alle Teilmengen von Attributen, die das Attribut
plz enthalten sind identifizierende Attributmengen.
r2 = {< 1, M eier, 3006, Bern >,
< 2, M üller, 3006, Bern >,
< 3, Gloor, 4001, Basel >}
In diesem Beispiel existieren zwei Schlüssel und zwar {l_nr} und {name}.
r3 = {< 1, M eier, 3006, Bern >,
< 1, M üller, 3006, Bern >,
< 1, M eier, 4001, Basel >}
3-5
In diesem Beispiel sind die Attributmengen {name, plz} und {name, ort} Schlüssel für die
Relation.
r4 = {< 1, M eier, 3006, Bern >}
In diesem Beispiel ist jedes Attribut ein Schlüssel, da jedes Tupel (das einzige) durch jedes
Attribut eindeutig identifiziert wird. Jede Teilmenge von Attributen ist eine identifizierende
Menge.
3.4
Syntaktische Schlüssel
Aus dem Beispiel 3.5 sieht man, dass jede Relation verschiedene identifizierende Mengen
und verschiedene Schlüssel besitz, obwohl alle Relationen über das gleiche Relationenschema
definiert sind. In der Praxis ist diese Art der Identifikation nicht brauchbar. Wir brauchen
einen Schlüsselbegriff, der zeitinvariant ist. Das heisst, der Schlüssel darf nur vom Relationenschema abhängig sein.
Ein solcher Schlüssel nennt man dann syntaktisch, weil er vom Designer des Relationenschemas
gesetzt wird und nich von den Tupeln einer Relation abgeleitet wird (Semantik).
Definition 3.8 [Syntaktischer Schlüssel] Ein syntaktischer Schlüssel für ein
Relationenschema R ist eine Menge K := {A1 , . . . , Ak } ⊆ R von Attributen,
welche der folgenden lokalen Integritätsbedingung genügt:
bK (r(R)) =
(
true falls ∀t1 , t2 ∈ r(R) : [t1 6= t2 ⇒ ∃Aj ∈ K : t1 (Aj ) 6= t2 (Aj )]
f alse sonst
Im Unterschied zum semantischen Schlüssel können wir keine Minimalitätsbedingung stellen,
da wir unter anderm auch einelementige Relationen zulassen wollen. Wir können aber folgendes fordern:
Eine Attributmenge K1 kann nicht als Schlüssel definiert werden, falls eine Attributmenge
K0 existiert, die als Schlüssel definiert ist und K0 ⊂ K1 (echte Teilmenge).
Bemerkung 3.8 [Schlüssel und Nullwerte] Nach Definition wird ein Tupel einer Relation durch einen Schlüssel eindeutig identifiziert. Daher darf kein Attribut des Schlüssels
einen Nullwert enthalten. Ein Objekt kann nicht durch etwas, das unbekannt ist, eindeutig
identifiziert werden. Diese Bedingung wird oft als Schlüsselintegrität bezeichnet.
Im allgemeinen ist es möglich, dass zu einem Relationenschema mehrere syntaktische Schlüssel
existieren.
Beispiel 3.6 [Mehrere Schlüssel in einer Relation] Als Beispiel betrachten wir eine Personalkartei in einer schweizer Firma. Jede Person hat (unter anderem) eine Personalnummer
p_nr und eine AHVNummer. Die Personalnummer p_nr sei eine eindeutige fortlaufende Nummer die einmal vergeben wird und nicht geändert werden kann. Für das Relationenschema
personal existieren die beiden Schlüssel K0 ={p_nr} und K1 = {ahv_nr}.
Dies führt uns nun zur nächsten Definition.
Definition 3.9 [Primärschlüssel] Ein Primärschlüssel (engl. primary Key) für
ein Relationenschema R ist ein ausgezeichneter Schlüssel. Weitere Schlüssel von
R (falls vorhanden) heissen dann Kandidatschlüssel (candidate Key).
3-6
Die Wahl des Primärschlüssels unter den möglichen Schlüsseln spielt vom Modell her keine
Rolle. In der Praxis ist es aber wichtig, dass der Primärschlüssel nie ändert. Wir werden auf
dieses Problem im Abschnitt 3.6 noch zurückkommen.
Beispiel 3.7 [Wahl des Primärschlüssels] Im Beispiel 3.6 wird man die Personalnummer
p_nr als Primärschlüssel wählen, weil diese nicht geändert werden kann.
3.5
Datenbankschema und Datenbank
Definition 3.10 [Datenbankschema und Datenbank] Eine endliche nichtleere
Menge von Relationenschematas S := {R1 , . . . , Rp } über das Universum U heisst
Datenbankschema, eine Menge erweiterter Relationenschemata lokal erweitertes
Datenbankschema.
Ein Datenbankwert (kurz: Datenbank) über einem Datenbankschema S ist eine
Menge von Relationen d := {r1 (R1 ), . . . , rp (Rp )}. Eine Datenbank d über S wird
mit d(S) bezeichnet. eine Relation r(R) ∈ d(S) heisst Basisrelation
Im relationalen Modell ist eine Datenbank also nichts anderes als eine Menge von Relationen.
Wenn wir für die Relationen die Interpretation als Tabellen nehmen, so können wir sagen:
Eine relationale Datenbank ist eine Datenbank die nur aus Tabellen
besteht
Bei der Definition der Relationen haben wir lokale Integritätsbedingungen eingeführt. Lokale
Integritätsbedingungen beziehen sich immer auf nur eine Relation. Entsprechend können wir
nun globale Integritätsbedingungen definieren, welche Beziehungen zwischen mehreren Relationen ausdrücken.
Definition 3.11 [Globale Integritätsbedingungen] Eine Menge von Abbildungen Γ := {γ|γ : {d|d(S)} −→ {true, f alse}} nennt man eine Menge globaler
Integritätsbedingungen für das Datenbankschema S. Dann heisst S := (S, Γ) global erweitertes Datenbankschema. d(S) ist eine Datenbank d(S) mit γ(d) = true
für alle γ ∈ Γ (kurz Γ(d) = true). Die Menge aller gültigen Datenbanken wird mit
DAT (S) := {d|d(S)} definiert.
Beispiel 3.8 [Globale Integritätsbedingung] Wir betrachten die beiden Relationenschematas bestellung und b_p im Lieferantenbeispiel im Anhang A. Wir möchten sicherstellen, dass zu jeder Bestellung mindestens ein Bestellposten in b_p existiert. Diese Forderung
kann mit Hilfe einer globalen Integritätsbedingung erzwungen werden.
γ1 (d) :=


 true
falls ∀t1 ∈ r(bestellung) : [∃t2 ∈ r(b_p) :
t1 (b_nr) = t2 (b_nr)]

 f alse sonst
3-7
3.6
Fremdschlüssel (foreign key)
Der Begriff des Fremdschlüssels ist im relationalen Modell sehr wichtig. Es ist im Modell
das einzige Konzept, das es uns erlaubt, Tupel aus verschiedenen Relationen in Beziehung zu
bringen.
Definition 3.12 [Fremdschlüssel] Eine Fremdschlüsselbeziehung zwischen zwei
Relationen r1 (R1 ) und r2 (R2 ) ist ein Ausdruck der Form X(R1 ) → Y (R2 ) mit
X ⊆ R1 und Y ⊆ R2 , welcher den folgenden Bedingungen genügt:
• Y ist der primärschlüssel für R2
• {t(X)|t ∈ r1 (R1 )} ⊆ {t(Y )|t ∈ r2 (R2 )}
X nennt man Fremdschlüssel für R1 bezüglich Y in R2 .
Aus der Definition geht sofort hervor, dass die Anzahl Attribute in X und Y dieselbe sein
muss. Ferner muss für die Domänen gelten: dom(X) ⊆ dom(Y ).
Man kann Fremdschlüssel mit Hilfe von Domänen ausdrücken. Die Domäne eines Fremdschlüssels X für r1 (R1 ) bezüglich Y in r2 (R2 ) ist gleich der Menge {t(Y )|t ∈ r2 (R2 )}.
bestellung
b_nr
l_nr
bestelldatum
.
10
.
123
.
2
22−apr−1995
2
05−jul−1996
lieferant
l_nr
name
...
.
X(bestellung)
Y(lieferant)
2
.
Haller
.
Abbildung 3-2: Fremdschlüssel X(bestellung) → Y (lief erant)
Beispiel 3.9 [Beispiel für Fremdschlüssel] In unserem Beispiel wird die Tatsache, dass
jede Bestellung zu einem (und nur einem) Lieferanten gehört mit Hilfe einer Fremdschlüsselbeziehung ausgedrückt. Der Primärschlüssel für Lieferanten ist das Attribut l_nr. Das
Attribut l_nr in den Bestellungen ist dann der Fremdschlüssel und wir können die folgende
Fremdschlüsselbeziehung einführen:
X(bestellung) → Y (lief erant) mit X = {l_nr}, Y = {l_nr}
3-8
Mit dieser Modellierung gehört jede Bestellung zu genau einem Lieferanten. Zu einem Lieferanten existieren keine, eine oder mehrere Bestellungen. Das Beispiel ist in der Abb. 3-2
dargestellt.
Beispiel 3.10 [M zu M Beziehung mit Fremdschlüssel] Mit Hilfe von Fremdschlüsselbeziehungen können aber auch, mit Hilfe einer Hilfsrelation, kompliziertere Beziehungen
dargestellt werden. In unserem Beispiel können in einer Bestellung mehrere Produkte vorkommen. Umgekehrt kann dasselbe Produkt aber auch in verschiedenen Bestellungen vorkommen.
Um diese Tatsache mit Fremdschlüsselbeziehungen zu modellieren, wird die Hilfsrelation b_p
eingeführt, die im wesentlichen aus zwei Fremdschlüsseln b_nr und p_nr besteht. ({b_nr,
p_nr} is dann Primärschlüssel).
Wir definieren nun die zwei folgenden Fremdschlüsselbeziehungen:
X(b_p) → Y (bestellung) mit X = {b_nr},
Y = {b_nr}
X(b_p) → Y (produkt)
mit X = {p_nr},
Y = {p_nr}
Die neue Relation b_p nennt man auch eine Beziehungsmenge. Das Beispiel ist in der Abb.
3-3 dargestellt.
bestellung
produkt
p_nr
b_nr l_nr bestelldatum ...... ...
X(b_p)
lagermenge
...
.
.
10
.
.
bezeichnung
2
22−apr−1995
2
.
.
Y(bestellung)
Chardonnay 1989
X(b_p)
260
Y(produkt)
b_p
b_nr
p_nr
menge
.
10
.
.
2
37
Abbildung 3-3: Die Beziehungsmenge b_p
Bemerkung 3.9 [Fremdschlüssel als Integritätsbedingungen] Fremdschlüsselbeziehungen können als spezielle globale Integritätsbedingungen aufgefasst werden. In der Literatur
wird dies oft als Fremdschlüsselintegrität bezeichnet.
Bemerkung 3.10 [Fremdschlüssel und Nullwerte] Es stellt sich oft die Frage, ob ein
Fremdschlüssel auch einen Nullwert enthalten darf (d.h., alle Attribute des Fremdschlüssels
sind null). Je nach Bedeutung des Fremdschlüssels kann ein Nullwert sinnvoll sein oder nicht.
In unserem Beispiel sind Bestellungen, bei denen der Lieferant unbekannt ist eher nicht sinnvoll.
3-9
3.7
Objektidentität (Surrogate)
In einer Datenbank sind Modelle von Entitäten der realen Welt wie Lieferanten, Bestellungen,
Produkte usw. gespeichert. Da alle Entitäten in der realen Welt unterscheidbar sind, müssen
sie auch im Modell (Datenbank) unterscheidbar sein. Im Relationenmodell geschieht dies
durch einen Primärschlüssel, der vom Benutzer definiert und kontrolliert wird. Das Finden
eines sinnvolen Primärschlüssels ist aber nicht in jedem Fall einfach.
Beispiel 3.11 [Probleme mit semantischen Schlüssel] Schon in unserem Beispiel ist es
nicht einfach, einen sinnvollen Primärschlüssel für die Relation lieferant zu finden (weder der
Name noch die Adresse usw. wären eindeutig) hätten wir nicht eine fortlaufende (eindeutige)
Lieferanten-Nummer (l_nr) eingeführt.
Es geht also darum, einen Primärschlüssel zu finden, der eindeutig ist und wenn möglich im
Laufe der Zeit nie ändert.
Bemerkung 3.11 [Veränderbarer Primärschlüssel] Im Prinzip wird vom Modell nicht
gefordert, dass der Primärschlüssel nicht verändert wird. Dies ist aber in der Praxis aus zwei
Gründen erwünscht:
1. Wenn der Primärschlüssel im Laufe der Zeit ändert, kann dies zu Identifikationsproblemen führen (zum Beispiel bei Statistiken).
2. Da in Fremdschlüsseln der Wert eines Primärschlüssels gespeichert ist, müssen eventuell
sehr viele Tupel geändert werden, wenn ein Primärschlüssel geändert wird.
Um dieses Problem zu lösen hat Codd vorgeschlagen sogenannte Surrogates einzuführen.
Definition 3.13 [Surrogate] Ein Surrogate ist ein vom System generierter und
verwalteter Primärschlüssel. Der Schlüssel ist nicht nur pro Relation eindeutig,
sondern über die gesammte Datenbank. Der Surrogate-Wert bleibt während der
ganzen Lebensdauer eines Objektes in der Datenbank gleich.
Die Vorteile eines Surrogates
• Die Identität eines Objektes ist unabhängig vom Wert des Objekts. Somit wird es
möglich, dass zwei verschiedene Objekte denselben Wert haben.
• Ein Surrogate ändert seinen Wert nie. Damit sind die beiden Probleme Identifikationsschwierigkeiten und Nachführen von Fremdschlüsseln gelöst.
• Die Primärschlüssel-Integrität wird voll vom System garantiert.
Bemerkung 3.12 [Der Surrogate wird vom System verwaltet] Der Unterschied zwischen einer vom Anwender eingeführten Laufnummer wie in unserem Beispiel und einem Surrogate ist, dass die Laufnummer von der Applikation generiert und gewartet werden muss.
Der Surrogate wird vom DBMS generiert und gewartet.
3.8
Fremdschlüssel und Löschen von Tupel
Am Ende dieses Kapitels wollen wir noch kurz betrachten, was passiert, wenn ein Tupel
gelöscht wird, dessen Primärschlüsselwert in einer anderen Relation als Fremdschlüsselwert
auftritt. Damit beim Löschen von Tupeln die referentielle Integrität der Datenbank erhalten
bleibt, können die drei folgenden Strategien angewendet werden.
3-10
1. Restricted delete
Solange Fremdschlüsselwerte existieren, die dem zu löschenden Primärschlüsselwert entsprechen,
wird die Löschoperation nicht akzeptiert. Diese Funktion bezeichnen wir mit res.
2. Cascaded delete
Alle Tupel, deren Fremdschlüssel dem gelöschten Primärschlüssel entsprechen, werden
ebenfalls gelöscht. Diese Funktion bezeichnen wir mit cas.
3. Nullify
Alle Fremdschlüsselwerte, die dem gelöschten Primärschlüssel entsprechen, werden auf
Null gesetzt. Dies bedingt natürlich, dass für den Fremdschlüssel Nullwerte zugelassen
sind. Diese Funktion bezeichnen wir mit nul.
Statt den Wert auf Null zu setzen kann man sich auch vorstellen, den Wert auf eine
definierte Konstante zu setzen. Dies bedingt allerdings, dass dieser konstante Wert in der
entsprechenden Relation als Primärschlüsselwert vorhanden ist. Diese Variante werden
wir in diesem Kurs nicht weiter verfolgen.
Beispiel 3.12 [Löschen von Tupel] Wir wollen für jeden der drei Fälle noch ein Beispiel
angeben:
1. Restricted:
Ein Kunde darf erst gelöscht werden, wenn keine offene Rechnungen für diesen Kunden
existieren.
2. Cascaded:
Wenn ein Produkt gelöscht wird, so wird auch die entsprechende Stückliste gelöscht.
3. Nullify:
Wenn in einer Firma eine Abteilung geschlossen wird, sollen nicht alle entsprechenden
Mitarbeiter gelöscht werden. Für diese Mitarbeiter wird die Abteilung vorläufig auf null
(Abteilung unbekannt) gesetzt.
3.9
Notation für Relationen
Wir wollen hier noch festlegen, wie wir die einzelnen Elemente des relationalen Modells
notieren wollen.
3.9.1
Relationen
Eine Relation wird als Tupel, bestehend aus Attribute der Relation, Primärschlüssel sowie
eine optionale Liste von Fremd- und Kandidatschlüssel geschrieben.
Relation = ({Attributliste},{Primärschlüssel},[keys],. . . )
Beispiel:
Person = ({p_nr, name nw, a_nr, ort, geburtsdatum,. . . },{p_nr})
Wird hinter einem Attribut nw geschrieben, so bedeutet dies, dass für dieses Attribut Nullwerte
erlaubt sind. In allen anderen Fällen sind Nullwerte nicht erlaubt.
3-11
3.9.2
Kandidatschlüssel
Beim Kandidatschlüssel werden die Attribute des Kandidatschlüssels angegeben.
ks({Attributliste})
Beispiel:
ks({name, ort})
3.9.3
Fremdschlüssel
Beim Fremdschlüssel werden die Attribute des Fremdschlüssels, die entsprechende Relation,
auf die der Fremdschlüssel zeigt und, der Löschmodus geschrieben.
fs({Attributliste}, Relation, Löschmodus)
Beispiel:
fs({a_nr}, Abteilung, res)
Das Beispiel bedeutet, dass die Abteilungsnummer (a_nr) Fremdschlüssel auf die Relation
Abteilung ist, und dass eine Abteilung nur gelöscht werden kann, wenn keine Personen in
dieser Abteilung arbeiten.
3-12
3.10
Übungen
Aufgabe 3.1 [Fragen zur Theorie]
a) Welche Bedeutung hat der Fremdschlüssel im Relationenmodell? Illustrieren Sie die
Antwort an Hand eines Beispiels.
b) Warum sind Nullwerte für Attribute des Primärschlüssels nicht zulässig?
c) In welchen Fällen sind Nullwerte für einen Fremdschlüssel sinnvoll? Geben Sie ein
Beispiel an.
d) Gegeben sei das Universum U = {name, vorname, alter}. Wieviele verschiedene Relationenschematas sind über dieses Universum möglich?
e) Gegeben sei ein Datenbankschema S = {R1 , Rn }. Wann sind zwei Datenbanken d1 (S)
und d2 (S) über dieses Schema gleich?
Aufgabe 3.2 [Relationen und Tupel] Gegeben sei das folgende Relationenschema R =
{Zahl1 , Zahl2 } mit zwei Attributen und den folgenden Domänen:
dom(Zahl1 ) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
dom(Zahl2 ) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
Wieviele verschiedene Relationen über R sind in den nachfolgenden Fällen möglich? Begründen Sie ihre Antworten.
a) {Zahl1 , Zahl2 } ist Primärschlüssel.
b) {Zahl1 } ist Primärschlüssel und {Zahl2 } ist Kandidatschlüssel.
c) {Zahl1 } ist der einzige Schlüssel also auch Primärschlüssel.
d) {Zahl1 , Zahl2 } ist Primärschlüssel und es soll die folgende lokale Integritätsbedingung
erfüllt sein:
b(r(R)) =
(
true ∀t ∈ r(R) : t(Zahl1 ) ≤ t(Zahl2 )
f alse sonst
3-13
Kapitel 4
Relationenalgebra
Das Relationenmodell bietet einen Satz von im Modell implizit enthaltenen Operationen, die
wir generische Operationen nennen werden. Implizit definiert heisst, dass die Semantik dieser
Operationen nicht anwendungsspezifisch ist, sondern direkt zu den Konzepten des Relationenmodells gehört. Eine mögliche Grundlage für diese Operationen ist die Relationenalgebra.
Bemerkung 4.1 [Dynamischer Teil des Modells] Sehr oft wird bei der Präsentation des
relationalen Modells die dynamische Seite des Modells vergessen. Ein DBMS, welches nicht
mindestens die im folgenden beschriebenen Operationen zur Verfügung stellt, kann man nicht
als relationales Datenbanksystem bezeichnen.
4.1
Eigenschaften der Relationenalgebra
Die Relationenalgebra hat eine ganze Reihe von positiven Eigenschaften, welche wir in der
folgenden Liste festhalten wollen.
Deskriptive Sprache Die Relationenalgebra ist in dem Sinne deskriptiv, dass keine Schleifen
oder Rekursionen erlaubt sind. Wir arbeiten einfach mit Mengen und Mengenoperationen. Die Sprache ist immer noch prozedural in dem Sinne, dass ein Ausdruck der Sprache
angibt, welche Operationen ausgeführt werden sollen um das Resultat zu erreichen. Eine
vollständig deskriptive Sprache beschreibt aber nur das gewünschte Resultat. Beispiele
für solche Sprachen sind sogenannte Relationenkalküle.
Abgeschlossenheit Die Relationenalgebra ist abgeschlossen unter dem zugrundeliegenden
Datenbankmodell. D.h., das Resultat jeder Operation ist wieder eine Relation, auf die
die Operatoren der Algebra angewendet werden können.
Adäquatheit oder Ausdruckskraft Adäquatheit bedeutet, dass alle Konzepte des zugrundeliegenden Datenbankmodells durch die Anfragesprache ausgenutzt werden sollen.
Man spricht in diesem Zusammenhang auch von der Ausdruckskraft einer Sprache. Die
Relationenalgebra dient als Grundlage für das Messen der Ausdruckskraft von anderen
Sprachen. Eine Sprache, die mindestens die Ausdruckskraft der Realtionenalgebra besitzt, heisst Codd-vollständig.
Effiziente Implementierbarkeit Alle Operatoren der Relationenalgebra sind effizient implementierbar. Die schlimmsten Operationen sind von quadratischer Komplexität (gemessen
an der Anzahl Tupel in einer Relation).
4-1
Optimierbarkeit Die Abgeschlossenheit der Anfragesprache ist für die Optimierung der
Anfragen besonders wichtig. Anfragen können algebraisch optimiert werden. D.h., ein
relationaler Ausdruck wird durch einen äquivalenten Ausdruck ersetzt, der effizienter
abgearbeitet werden kann. Bei der Umformung sind nur die Regeln der Relationenalgebra einzuhalten.
Sicherheit Sicher ist eine Anfragesprache, wenn jeder syntaktisch korrekte Anfrageausdruck
in endlicher Zeit ein endliches Ergebnis liefert. Die Relationenalgebra ist sicher, da sie
keine Schleifen und keine Rekursion zulässt. Im Gegensatz dazu ist jede normale Programmiersprache nicht sicher.
Orthogonalität Alle Operatoren der Relationenalgebra können frei und ohne Einschränkungen miteinander kombiniert werden. Die Relationenalgebra ist orthogonal.
Wir werden im Abschnitt 4.3 auf einige der hier angegebenen Punkte zurückkommen.
4.2
Die Operatoren der Relationenalgebra
Standardmässig umfasst die Relationenalgebra die Selektion σ, die Projektion π, den natürlichen Verbund ⊲⊳, die Mengenoperationen ∪, \ sowie die Umbenennung β.
Die nachfolgenden Beispiele beziehen sich alle auf die im Anhang A definierten Relationen.
4.2.1
Selektion
Das Symbol für die Selektion ist σ. Die Selektion wählt Tupel aus einer Relation aus. Das
Ergebnis ist wieder eine Relation über das gleiche Relationenschema. Als Selektionsbedingungen sind mindestens “Attribut-Konstante-Vergleich” und “Attribut-Attribut-Vergleich” zugelassen. Die Selektion ist in der Abb. 4-1 schematisch dargestellt.
Resultat von σ
Abbildung 4-1: Die Selektion
Definition 4.1 [Selektion] Gegeben seien r ∈ Rel(R), A, B ∈ R, mit dom(A) =
dom(B), a ∈ dom(A) und θ ∈ {<, ≤, =, >, ≥, 6= like}
σAθa (r) := {t|t ∈ r ∧ t(A)θa}
σAθB (r) := {t|t ∈ r ∧ t(A)θt(B)}
4-2
Beispiel 4.1 [Selektionen] Als erstes wollen wir alle Lieferanten mit Namen “Meier” auswählen.
σN ame=′ M eier′ (lief erant)
Das Resultat dieser Selektion ist in der Abbildung 4-2 angegeben.
l_nr
6
7
8
lieferant
name ort
Meier Bern
Meier Winterthur
Meier Biel
plz
3008
8402
3210
Abbildung 4-2: Resultat einer Selektion
Als zweites Beispiel wollen wir die Werte von zwei Attributen vergleichen. Wir wählen alle
Produkte, deren Lagermenge kleiner ist als die minimale Menge (min_lagermenge) ist.
σlagermenge≤min_lagermenge (produkt)
Das Resultat dieser Selektion ist in der Abbildung 4-3 angegeben.
p_nr
12
17
bezeichnung
Luins
Armagnac 45%
produkt
jahrg liefereinheit
2005
6
1948
1
lagermenge
54
13
min_l
60
20
Abbildung 4-3: Resultat einer Selektion mit Vergleich zweier Attribute
Bemerkung 4.2 [Boolsche Ausdrücke] “Atomare” Bedingungen der Form AθB dürfen
auch mit den logischen Operatoren ∧ (and), ∨ (or) und ¬ (not) gegebenenfalls mit einer
entsprechenden Klammerung zu einer komplexeren Bedingung C zusammengefasst werden.
Wir können also zum Beispiel die Anfrage
σlagermenge≤20 (σjahrgang=2000 (produkt))
kürzer als
σlagermenge≤20∧jahrgang=2000 (produkt))
schreiben.
Die in einer allgemeinen Bedingung C vorkommenden Attribute bezeichnen wir mit attr(C).
4.2.2
Projektion
Das Symbol für die Projektion ist π. Die Projektion bildet aus einer Relation neue Tupel. In
jedem Tupel werden nur die Werte von gegebenen Attributen berücksichtigt. In der neuen
Relation müssen Tupel mit gleichem Wert eliminiert werden, da sonst das Resultat keine
Menge und somit auch keine Relation ist. Das Resultat der Projektion ist eine Relation über
das Relationenschema das ensteht, wenn nur die gegebenen Attribute berücksichtigt werden.
Die Projektion ist in der Abb. 4-4 schematisch dargestellt.
4-3
Resultat von π
Abbildung 4-4: Die Projektion
Definition 4.2 [Projektion] Gegeben seien r ∈ Rel(R) und X ⊆ R
πX (r) := {t(X)|t ∈ r}
Beispiel 4.2 [Projektionen] Wir wollen alle Orte bestimmen, wo mindestens ein Lieferant
existiert
πort (lief erant)
Das Resultat dieser Projektion ist in der Abbildung 4-5 angegeben.
lieferant
ort
Bern
Thun
Lausanne
Winterthur
Biel
Genf
Sion
Abbildung 4-5: Resultat der Projektion
Man beachte, dass die Projektion wieder eine Relation als Resultat liefert. Daher sind auch
alle Tupel verschieden voneinander. “Bern”, “Thun” und “Genf” kommen im Resultat nur
einmal vor.
4.2.3
Natürlicher Verbund
Das Symbol für den natürlichen Verbund ist ⊲⊳. Der natürliche Verbund oder natural Join
verknüpft zwei Relationen über allen gemeinsamen Attributen. Es werden Tupel mit gleichen
Attributwerten über die gemeinsamen Attribute zu einem neuen Tupel verbunden. Der natürliche Verbund ist in der Abb. 4-6 schematisch dargestellt.
4-4
A
a
a1
a2
a3
a4
a5
A
B
b
b1
b2
b3
b1
b5
b c
b1 c1
b2 c1
b3 c3
a
a1
a2
a3
a4
B
b
b1
b2
b3
b1
c
c1
c1
c3
c1
nat. Verbund
Abbildung 4-6: Der Verbund
Definition 4.3 [Verbund] Gegeben seien r1 ∈ Rel(R1 ) und r2 ∈ Rel(R2 )
r1 ⊲⊳ r2 := {t|t tupel über R1 ∪ R2 ∧ t(R1 ) ∈ r1 ∧ t(R2 ) ∈ r2 }
Bemerkung 4.3 [Verbund Spezialfälle] Speziell wird der Verbund im Falle R1 = R2 zum
mengentheoretischen Durchschnitt, im Falle R1 ∩ R2 = {} zum kartesischen Produkt.
Beispiel 4.3 [Verbunde] Wir möchten alle Lieferanten der Datenbank mit ihren noch offenen Bestellungen verbinden. Dazu selektieren wir alle Bestellungen die noch kein Lieferdatum
besitzen (Wert ist null) und verbinden anschliessend die resultierende Relation mit den Lieferanten. Nach der Selektion können wir noch das Lieferdatum wegprojezieren.
lief erant ⊲⊳ πl_nr,b_nr,Bestelldatum (σLief erdatum = null(Bestellung))
Das Resultat der Operation ist in der Abbildung 4-7 angegeben.
l_nr
1
1
1
2
2
2
3
7
7
9
name
Dettwiler
Dettwiler
Dettwiler
Haller
Haller
Haller
Walter
Meier
Meier
Grobet
Offene Bestellungen
ort
plz
b_nr
Bern
3016
13
Bern
3016
14
Bern
3016
15
Thun
3604
10
Thun
3604
11
Thun
3604
12
Thun
3602
17
Winterthur 8402
7
Winterthur 8402
19
Genf
1003
18
bestelldatum
2007-03-11
2007-04-12
2007-05-10
2007-04-21
2007-03-24
2007-05-10
2007-04-21
2006-12-29
2007-04-23
2007-05-03
Abbildung 4-7: Resultat des natürlichen Verbunds
Das einzige gemeinsame Attribut von Lieferant und Bestellung ist die Lieferanten Nummer l_nr. Für jeden Lieferanten entsteht pro Bestellung die er hat genau ein Tupel in der
Resultatrelation.
4-5
4.2.4
Mengenoperationen: Die Vereinigung und die Differenz
Die üblichen Mengenoperationen Vereinigung, Durchschnitt und Differenz können auf Relationen mit gleichem Relationenschema angewandt werden. Die Mengenoperationen sind in der
Abbildung 4-8 schematisch dargestellt.
r1
r1
r1
r2
r2
r2
Resultat von r1
r2
Resultat von r1 \ r2
Resultat von r1
r1 \ (r1 \ r2)
r2 =
Abbildung 4-8: Die Mengenoperationen
Definition 4.4 [Vereinigung und Differenz] Gegeben seien r1 , r2 ∈ Rel(R)
r1 ∪ r2 := {t|t ∈ r1 ∨ t ∈ r2 }
r1 \ r2 := {t|t ∈ r1 ∧ t ∈
/ r2 }
Die Operation des Durchschnitts zweier Mengen braucht man nicht zu definieren, da diese aus
der Mengendifferenz folgendermassen gewonnen werden kann:
r1 ∩ r2 := r1 \ (r1 \ r2 )
Beispiel 4.4 [Vereinigung] Als Beispiel wollen wir das Resultat von zwei Selektionen vereinigen.
σname=′ M eier′ (lief erant) ∪ σname=′ F avre′ (lief erant)
Das Resultat der Operation ist in der Abbildung 4-9 angegeben.
l_nr
6
7
8
5
lieferant
name ort
Meier Bern
Meier Winterthur
Meier Biel
Favre Lausanne
plz
3008
8402
3210
1205
Abbildung 4-9: Resultat einer Vereinigung
Bemerkung 4.4 [Kompatible Schemen] Wir haben gesagt, dass die Mengenoperationen nur auf Relationen mit gleichem Relationenschema anwendbar sind. Diese Aussage kann
etwas abgeschwächt werden, indem wir nur verlangen, dass beide Relationenschematas gleichviele Attribute besitzen und dass Domänen der Attribute übereinstimmen. Mit Hilfe der
Umbenennung β, die im nächsten Abschnitt erklärt ist, können dann zwei Relationen, die den
angegebenen Bedingungen genügen, vereinigt werden.
4-6
4.2.5
Umbenennung
Das symbol für die Umbenennung ist β. Diese Operation wird oft vergessen. Sie ist aber
unbedingt erforderlich, um etwa zwei Relationenschemata für eine Vereinigung kompatibel
zu machen. Auch für Selfjoins (Join einer Relation mit sich selbst) ist die Umbenennung
unbedingt erforderlich.
Definition 4.5 [Umbenennung] Gegeben seien r ∈ Rel(R), A ∈ R, B ∈
/ R\
′
{A}, R := (R \ {A}) ∪ {B}, dom(A) = dom(B)
βB←A (r) := {t′ |∃t ∈ r : t′ (R \ {B}) = t(R \ {A}) ∧ t′ (B) = t(A)}
Beispiel 4.5 [Umbenennung ist notwendig] Zur Darstellung der Umbenennung wollen
wir eine Relation person in unserer Beispieldatenbank anschauen. In person ist das Feld
mutter Fremdschlüssel auf person.
Falls wir nun alle Personen mit dem Namen der Mutter ausgeben möchten, so brauchen wir
die Umbenennung, damit das Feld p_nr mit dem Feld mutter verbunden werden kann.
Der Verbund soll über die Personen Nummer (p_nr) und der Nummer der Mutter durchgeführt
werden. Daher erzeugen wir als erstes eine Relation mit den Attributen mutter und mname
durch Umbenennung von p_nr und name. In der Resultatrelation sind alle Personen vorhanden.
Das gewünschte Resultat kann nun durch einen Verbund mit der Relation person erzielt
werden.
πp_nr,name,mutter (person) ⊲⊳ βmutter←p_nr,mname←name (πp_nr,name (person))
Das Resultat dieser Operation ist in der Abbildung 4-10 angegeben.
p_nr
7
8
9
12
13
16
18
19
20
21
22
23
lieferant
name
mutter
Glauser
6
Meier
6
Meier
6
Glauser
7
Rohner
7
Meier
11
Glauser
14
Glauser
14
Glauser
14
Rohner
13
Rohner
13
Meier
17
mname
Meier
Meier
Meier
Glauser
Glauser
Meier
Glauser
Glauser
Glauser
Rohner
Rohner
Meier
Abbildung 4-10: Resultat eines Selfjoins
Bemerkung 4.5 [Weitere Operationen] Wir verzichten hier auf die Definition von weiteren Operationen (wie z.B. kartesisches Produkt, Division, Totalprojektion, Equijoin usw.), da
uns schon jetzt eine “vollständige” Menge von Operationen zur Verfügung steht. Insbesondere
lassen sich viele weitere Operationen allein unter Verwendung der hier eingeführten Operationen definieren.
4-7
4.3
Ausdrücke der Relationenalgebra
Wir gehen davon aus, dass eine relationale Datenbank aus einer Menge von Relationen besteht.
Der Benutzer kennt den Inhalt der Relationen a priori nicht, sondern nur die Schemainformationen. Um mit der Datenbank arbeiten zu können braucht es eine Sprache um Abfragen
zu formulieren. Insbesondere braucht es abstrakte Ausdrücke, die anstelle der konkreten Relationen der Name des entsprechenden Schemas enthalten. Mit Hilfe der vorher definierten
Operationen können wir nun die Syntax der Sprache definieren.
Definition 4.6 [Ausdrücke der Relationenalgebra] Sei D := (R, Γ) ein erweitertes Datenbankschema mit R = {R1 , . . . , Rk }. Die Menge RAD der Ausdrücke der Relationenalgebra (über D) wird rekursiv wie folgt definiert:
(i) ∀Ri ∈ R : Ri ∈ RAD
(ii) Sind E1 , E2 ∈ RAD und R(E1 ), R(E2 ) bezeichnen die durch E1 bzw E2
definierten Relationenschematas, dann gilt:
(a)
(b)
(c)
(d)
(e)
σC (E1) ∈ RAD falls C eine Bedingung ist und attr(C) ∈ R(E1 )
πX (E1) ∈ RAD falls X ⊆ R(E1 )
E1 ⊲⊳ E2 ∈ RAD
E1 ∪ E2 , E1 \ E2 ∈ RAD falls R(E1 ) = R(E2 )
βB←A (E1) ∈ RAD falls A ∈ R(E1 ) ∧ B ∈
/ (R(E1 ) \ {A})
(iii) nur solche Ausdrücke gehören zu RAD , welche durch endlich wiederhohlte
Anwendung von (i) und (ii) entstehen.
(iv) Sei E ∈ RAD so schreiben wir vE (d) für die Auswertung von E bezüglich
d ∈ Dat(R)
Zwei Ausdrücke E1 , E2 ∈ RAD heissen äquivalent, i.Z. E1 ≈ E2 , falls gilt:
∀d ∈ Dat(R)) : vE1 (d) = vE2 (d)
Mit der Definition 4.6 ist die Relationenalgebra nun vollständig definiert. Wir sind nun in der
Lage zu zeigen, dass die Relationenalgebra wirklich die Eigenschaften, die wir im Abschnitt
4.1 angegeben haben besitzt.
4.3.1
Abgeschlossenheit und Adequatheit
Nach Definition liefert jede Operation eine Relation. Darum wurde auch gefordert, dass die
Mengentheoretischen Operationen (Vereinigung und Differenz) nur auf kompatiblen Relationen (d.h., Relationen mit gleichem Relationenschema) erlaubt sind. Da nach Definition 4.6
ein Ausdruck entweder eine Relation oder das Resultat einer Operation auf Relationen ist, so
muss die Relationenalgebra abgeschlossen sein.
Die Adequatheit der Sprache ist je nach Standpunkt gegeben oder nicht gegeben. Wie im
Abschnitt 4.1 schon gesagt ist vor allem die Ausdruckskraft der Sprache wesentlich. Das
heisst, welche Informationen können mit Hilfe der Sprache aus der Datenbank konstruiert
werden.
Die Ausdruckskraft der relationalen Algebra wird heute als Massstab für die Bewertung von
anderen Sprachen verwendet. Man verlangt, dass jede (relationale) Datenbanksprache LD
mindestens die Ausdruckskraft der relationalen Algebra besitzt. Sprachen, die mindestens die
Ausdruckskraft der relationalen Algebra besitzen heissen Codd-vollständig. In der nächsten
Definition wird dieser Begriff noch formal definiert.
4-8
Definition 4.7 [Codd-Vollständigkeit] Sei D := (R, Γ) ein erweitertes Datenbankschema und LD eine beliebige (relationale) Sprache. LD heisst Codd-vollständig,
wenn
∀E ∈ RAD ∃Ẽ ∈ LD mit E ≈ Ẽ
Wir wollen noch an dieser Stelle zeigen, dass die Ausdruckskraft der relationalen Algebra
beschränkt ist. Dies hat insbesondere damit zu tun, dass die Sprache sicher ist.
Zur Illustration wollen wir das Problem der transitiven Hülle einer binären Relation verwenden.
Definition 4.8 [Transitive Hülle] Sei R = {a, b} ein Relationenschema mit
dom(a) = dom(b) und r ∈ Rel(R). Die transitive Hülle von r (in zeichen r+ ), ist
wie folgt definiert:
• ∀(x, y) ∈ r ⇒ (x, y) ∈ r+
• (x, y) ∈ r+ ∧ (y, z) ∈ r+ ⇒ (x, z) ∈ r+
Beispiel 4.6 [Graphen] Als Beipiel betrachten wir die Relation
r
a
1
2
3
b
2
3
4
Eine solche zweistellige Relation kann auch als ungerichteten Graphen dargestellt werden.
Die in der Relation vorkommenden Konstanten (πa (r) ∪ πb (r)) repräsentieren die Ecken des
Graphen. Die Kanten sind durch die Tupel in der Relation r gegeben. In der Abbildung 4-11
ist die obige Relation als Graph dargestellt.
1
2
3
4
Abbildung 4-11: Relation als Graph
In der nächsten Tabelle ist die transitive Hülle unserer Relation angegeben.
r+
a
1
2
3
1
1
2
4-9
b
2
3
4
3
4
4
Die neuen Kanten im entsprechenden Graphen sagen nun aus, ob im ursprünglichen Graphen
einen Weg vom Knoten x nach dem Knoten y führt. In der Abbildung 4-12 ist der Graph der
transitiven Hülle dargestellt.
1
2
3
4
Abbildung 4-12: Transitive Hülle
Mit der relationalen Algebra können wir alle Wege der Länge ≤ k für eine gegebene Konstante
k finden. Der entsprechende Ausdruck lautet:
r ∪ πa,b1 (r ⊲⊳ βb1 ←b,b←a (r))
∪πa,b2 (r ⊲⊳ βb1 ←b,b←a (r) ⊲⊳ βb2 ←b,b1 ←a (r))∪
.
.
.
∪πa,bk (r ⊲⊳ βb1 ←b,b←a (r), . . . βbk ←b,bk−1 ←a (r))
Es ist also möglich, alle Wege zu finden, deren Länge kleiner als eine Konstante ist. Es ist aber
nicht möglich einen Ausdruck zu finden, der alle Wege in einem beliebigen Graphen findet.
Satz 4.1 [Unvollständigkeit der relationalen Algebra] r ∈ Rel({a, b}) sei
eine Relation in der Datenbank D und dom(a) = dom(b). Es existiert kein Ausdruck E ∈ RAD mit vE (d) = r+ .
Dieser Satz zeigt, dass die Ausdruckskraft der relationalen Algebra beschränkt ist; es gibt
Anfragen, die in Anwendungen nicht selten vorkommen, die in der relationalen Algebra nicht
ausgedrückt werden können.
4.3.2
Effiziente Implementierbarkeit
Es is sehr einfach, die Komplexität der verschiedenen Operatoren der relationalen Algebra
zu bestimmen (worst case). R1 , R2 seien zwei Relationenschematas und r1 ∈ REL(R1 ), r2 ∈
REL(R2 ) zwei Relationen mit n beziehungsweise m Tuppels. Bei der Selektion muss jedes
Tupple von r1 genau einmal behandelt werden, das heisst die Selektion hat die Komplexität
O(n).
Um die Projektion zu realisieren müssen die Tuppel nach den Projektionsattributen sortiert
werden um die Eindeutigkeit zu realisieren. Das heisst, die Komplexität ist O(n + n log(n)) =
O(n log(n)).
Der natürliche Verbund und die Mengenoperationen erfordern ein Sortieren beider Operanden.
Anschliessend werden die Tuppel beider Relationen genau ein mal gelesen. Das heisst, diese
Operationen haben die Komplexität:
O(n + m + n log(n) + m log(m)) = O(max(n, m) log(max(n, m)))
Die Umbenennung hat natürlich die Komplexität O(1).
4-10
Damit können alle algebraischen Operationen in polynomialer Zeit berechnet werden, und das
gleiche gilt auch für die Auswertung von Audrücken, da die endliche Addition von Polynomen
wieder ein Polynom ist.
4.3.3
Optimierung
Wir wollen hier auf einen wichtigen Aspekt der relationalen Algebra zu sprechen kommen.
Bevor ein Ausdruck der relationalen Algebra ausgewertet wird, kann er algebraisch optimiert
werden. Das heisst, rein durch Umformung des Ausdrucks versucht man die Ausführungszeit
zu verkürzen. Diese Art der Optimierung verwendet noch keine Strukturen wie B-Trees um
die Anfragen zu verschnellern.
Bemerkung 4.6 [SPJ-Ausdrücke] Wir betrachten bei der Optimierung zur Vereinfachung
nur sogenannte SPJ-Ausdrücke, die nur die Operatoren σ, π, ⊲⊳ und β enthalten. Die Mengenoperatoren lassen wir hier also weg.
Als erstes können wir beobachten, dass die Selektion und die Projektion im allgemeinen die
Anzahl Tuppel (und auch Spalten) in einer Relation verkleinert. Ferner ist zu beachten, dass
die Selektion schneller (O(n)) als die Projektion (O(n log(n)) ausgeführt werden kann. Ein
wichtiger Teil der Optimierung wird daher dafür sorgen, dass Selektionen so früh wie möglich
ausgeführt werden.
Die folgenden Rechenregeln können einfach abgeleitet werden und können bei der Optimierung
der Ausdrücke gut gebraucht werden:
(1) Sei E1 = R1 ⊲⊳ R2 , E2 = R2 ⊲⊳ R1 dann gilt E1 ≈ E2 .
(2) Sei E1 = R1 ⊲⊳ R2 , E2 = R1 ⊲⊳ (R1 ⊲⊳ R2 ) dann gilt E1 ≈ E2 .
(3) sei attr(C) ⊆ (R1 \ R2 ), E1 = σC (R1 ⊲⊳ R2 ), E2 = σC (R1 ) ⊲⊳ R2 dann gilt E1 ≈ E2 .
(4) Sei attr(C) ∈ R1, R1 ∩ R2 = ∅, E1 = πR1 (σC (R1 ⊲⊳ R2 )), E2 = σC (R1 ) dann gilt
E1 ≈ E2 .
(5) Sei A ⊆ R1 , E1 = πA (R1 ⊲⊳ R2 ), E2 = πA (πA∪(R1 ∩R2 ) (R1 ) ⊲⊳ R2 ) dann gilt E1 ≈ E2 .
usw
Wir werden keinen vollständigen Algorithmus für die Optimierung angeben. Der interessierte
Leser kann einen Algorithmus in [Ull82] finden. Allerdings wird dort nicht der natürliche
Verbund sondern das kartesische Produkt zweier Relationen verwendet.
Bemerkung 4.7 [Aufgabe des Optimizers] Es ist nicht die Aufgabe des Anwenders, die
Ausdrücke umzuformen und zu optimieren. Dies muss der Optimizer des Datenbankmanagementsystems übernehmen.
Beispiel 4.7 [Optimierung] Wir wollen nun mit Hilfe eines Beispiels sehen, wie die Optimierung funktioniert. In unserer Lieferantendatenbank könnte der folgende Ausdruck abgesetzt werden:
πp_nr,bezeichnung,name (σname=′ M eier′ ∧jahrgang≤2000∧lagermenge>=1 (lief erant ⊲⊳ bestellung ⊲⊳ b_p ⊲⊳
produkt))
Als erstes wird der Ausdruck so umgeformt, dass alle Konjuktionen bei den Selektionen verschwinden. Nach diesem Schritt sieht der Ausdruck folgendermassen aus:
4-11
πp_nr,bezeichnung,name (σname=′ M eier′ (σjahrgang≤2000 (σlagermenge>=1 (lief erant ⊲⊳ bestellung ⊲⊳
b_p ⊲⊳ produkt))))
Anschliessend wird der Syntaxbaum des Ausdrucks aufgebaut. Die inneren Knoten des Baumes
repräsentieren die Operationen und die Blätter sind die Operanden. In diesem Baum können
nun die Selektionen und die Projektionen so weit wie möglich nach unten verschoben werden.
In der Abbildung 4-13 sehen wir den Syntaxbaum zum zweiten Ausdruck.
π p_nr,bezeichnung,name
σname=’Meier’
σjahrgang<=2000
σlagermenge >= 1
lieferant
bestellung
b_p
produkt
Abbildung 4-13: Syntaxbaum vor der Optimierung
In der Abbildung 4-14 wurden alle Selektionen so weit wie möglich nach unten verschoben.
Dabei wurden alle angegebenen Regeln eingehalten.
π p_nr,bezeichnung,name
σname=’Meier’
lieferant
bestellung
b_p
σjahrgang<=2000
σlagermenge >= 1
produkt
Abbildung 4-14: Syntaxbaum mit optimierten Selektionen
4-12
In der Abbildung 4-15 wurden alle Projektionen so weit wie möglich nach unten verschoben.
Dabei wurden alle angegebenen Regeln eingehalten.
π p_nr,bezeichnung,name
π l_nr,name
σname=’Meier’
bestellung
lieferant
b_p
π p_nr,bezeichnung
σjahrgang<=2000
σlagermenge >= 1
produkt
Abbildung 4-15: Syntaxbaum mit optimierten Projektionen
In der Abbildung 4-16 wurden wo möglich die Selektionen wieder zu einer Selektion mit
Konjunktionen in der Bedingung zusammengefasst um Zugriffe auf die einzelnen Tupel zu
verringern.
π p_nr,bezeichnung,name
π l_nr,name
σname=’Meier’
lieferant
bestellung
π p_nr,bezeichnung
b_p
σjahrgang<=2000 ^ lagermenge>=1
produkt
Abbildung 4-16: Optimierter Syntaxbaum
Die algebraische Optiemierung ist nun für diesen Ausdruck abgeschlossen.
4-13
4.4
4.4.1
Übungen
Queries
Die folgenden Aufgaben beziehen sich alle auf die im Anhang des Skripts beschriebenen Datenbanken “Personenbeispiel” und “Lieferantenbeispiel”.
Aufgabe 4.1 [Beschreiben von Queries] Beschreiben Sie mit Worten das Resultat der
folgenden Ausdrücke.
a) πplz,ort (person)
b) βwohnort←ort (person)
c) πp_nr,name,hobby (σhobby=′ T heater′ (person ⊲⊳ pers_hobby))
d) person ⊲⊳ πhobby (pers_hobby)
e) σplz=3006 (person) ⊲⊳ σname=′ M eyer′ (person)
f) πname,vorname (σplz=8012 (person) ∪ σplz=4007 (person))
Die beiden folgenden Aufgaben sind mit dem Programm dbframe zu lösen.
Aufgabe 4.2 [Queries Personenbeispiel] Geben Sie Ausdrücke der Relationenalgebra an,
welche die folgenden Aufgaben lösen.
a) Bestimmen der Nummer, des Namens und des Vornamens aller Personen, die in Bern
wohnen und für die kein Vater und keine Mutter eingetragen ist.
b) Bestimmen des Namens aller Personen, die Theater als Hobby betreiben.
c) Bestimmen des Namens der Personen, die sowohl Golf wie Theater als Hobby betreiben.
d) Bestimmen des Namens der Personen, die Klavierspielen und Theater als Hobby haben
und keine weiteren Hobbies betreiben.
Aufgabe 4.3 [Queries Lieferantenbeispiel] Geben Sie Ausdrücke der Relationenalgebra
an, welche die folgenden Aufgaben lösen.
a) Selektieren aller Bestellungen mit Name und Nummer des Lieferanten, sowie das Bestelldatum.
b) Erstellen einer Liste aller bestellten und noch nicht gelieferten Produkte mit Bestellnummer, Produkt-Nummer, Produktbezeichnung und die bestellte Menge.
c Bestimmen aller Lieferanten, für die keine offenen Bestellungen existieren (Lieferdatum
ist null).
d) Erstellen einer Liste aller Lieferanten, für die eine Bestellung existiert wo sowohl das
Produkt mit Nummer 1 wie auch das Produkt mit Nummer 2 vorkommen.
e) Erstellen einer Liste aller Lieferanten, bei denen sowohl das Produkt Nummer 1 wie
auch das Produkt Nummer 2 bestellt wurden.
f) Suchen aller Lieferanten, die mindestens die gleichen Produkte wie der Lieferant mit
Nummer 5 geliefert haben oder liefern werden.
g) Suchen aller Lieferanten, die genau die gleichen Produkte wie der Lieferant mit Nummer
5 geliefert haben oder liefern werden.
4-14
4.4.2
Theoretische Aufgaben
Aufgabe 4.4 [Vollständigkeit der Algebra] Geben Sie ein einfaches Datenbankschema S
an, so dass eine Datenbank über S (d(S)) Informationen enthalten kann, die nicht mit Hilfe
der Relationenalgebra ermittelt werden kann. Dabei dürfen keine Beispiele aus dem Skript
verwendet werden.
4.4.3
Stundenplan Problem
Aufgabe 4.5 [Stundenplan]
Für diese Aufgabe existieren keine Daten in der Datenbank. Die Übung ist auf Papier zu lösen.
Gegeben seien die beiden folgenden Relationenschematas eines Stundenplan Verwaltungssystems.
lektion
= ({l_nr, von, bis}, {l_nr})
doz_lektion = ({doz_nr, l_nr}, {doz_nr, l_nr},
fs({l_nr}, lektion, cas))
Die Attribute von und bis geben den Beginn und das Ende (Datum und Zeit) einer Lektion an.
Die Relation doz_lektion ordnet jeder Lektion in der Relation lektion genau einen Dozenten
zu.
Gesucht:
Alle Paare von Lektionen, die sich zeitlich überschneiden und an denen der gleiche Dozent
beteiligt ist.
4-15
4-16
Kapitel 5
Datenbank Sprache (SQL)
In diesem Kapitel wird die standardisierte Datenbank Sprache SQL (Structured Query Language)
vorgestellt. Nachfolgend sind einige der wichtigsten Eigenschaften der Sprache SQL angegeben.
Standardisierung 1986 wurde SQL von ANSI als die Sprache für relationale Datenbanken
anerkannt (SQL/86 Standard). Dieser Standard ist 1989 erweitert worden (SQL/89
Standard). Der erste gemeinsame Standard von ISO und ANSI erschien 1992 und ist
unter dem Namen SQL:92 bekannt. Die neusten Normen sind SQL:1999 (oft auch
SQL:99 genannt) und SQL:2003. SQL:1999 und SQL:2003 führen viele objektorientierte
Konzepte ein. Daher spricht man auch von objektrelationalen Datenbanken. Der Standard SQL:2006 legt fest, wie SQL in Zusammenhang mit XML verwendet werden kann.
SQL:2008 ISO/IEC 9075:2008 ist die aktuelle Revision des SQL-Standards.
Deklarative Sprache SQL ist eine deklarative Sprache, definiert also nur was getan werden
soll, nicht wie. Dies hat zur Folge, dass mit SQL keine Iterationen möglich sind. Im
SQL-Standard SQL:1999 wurde neu die Rekursion eingeführt. Das führt natürlich dazu,
dass die Sprache nicht mehr sicher ist.
Mehrere Funktionen SQL ist gleichzeitig eine Datendefinitions- (DDL), Datenmanipulations(DML) und Datenkontroll-Sprache (DCL). In diesem Kapitel wird nur die Datenmanipulationssprache behandelt.
Allgemeines Interface SQL kann als allgemeines Interface zur Datenbank angesehen werden. Jeder Benutzer (bzw. Prozess) kommuniziert über SQL mit dem Datenbank-ManagementSystem, egal ob es sich um ein C-Programm mit embedded-SQL Befehlen, ein Programm aus einem Applikationsgenerator wie “windows4gl”, oder eine interaktive SQLSchnittstelle handelt.
Bemerkung 5.1 [SQL Standards] Der offizielle SQL Standard umfasst mehrere 1000 Seiten. In diesem Skript ist es also unmöglich, eine vollständige Beschreibung von SQL abzugeben.
Dieses Kapitel soll nur eine Einführung in die einfachsten Konzepte von SQL sein.
5.1
Allgemeines
Alle Beispiele in diesem Kapitel beziehen sich auf das Beispiel im Anhang A.
5-1
5.1.1
Notation
Die verwendete Notation für die Syntax der Befehle ist eine erweiterte BNF-Notation. Da die
Definition:
• Alle Keywörter sind gross geschrieben. Kleingeschriebene Wörter repräsentieren syntatktische Konstrukte (siehe auch die Konventionen weiter unten).
• Spezialcharakter wie <=, >, (, ) u.s.w haben die übliche Bedeutung.
• Das Zeichen "|" steht zwischen Alternativen.
• Das Klammerpaar "[" "]" zeigt an, dass das eingeschlossene Konstrukt fakultativ ist.
• Das Klammerpaar "{" "}" zeigt an, dass eine der in den Klammern angegebene Alternative obligatorisch ist.
• Der Konstrukt [ ]... bedeutet eine beliebige Wiederholung des eingeschlossenen Textes
(auch 0 mal)
• Konventionen
– Begriffe, mit “-name” als Suffix sind SQL-Identifier. Zum Beispiel “attribut-name”
oder “table-name”.
– “data-type” bezeichnet einen belibiegen atomaren Datentyp
– “literal” bezeichnet einen konstanten Wert.
Bemerkung 5.2 [Case Sensitivity] SQL ist nicht case sensitiv, es wird also zwischen
Gross- und Kleinschreibung nicht unterschieden. Das heisst, ob SeLect, SELECT oder select
geschrieben wird, es bedeutet immer dasselbe. Dies gilt auch für alle SQL-Identifiers (einzelne
Produkte weichen von dieser Regel ab).
5.2
Datenmanipulationssprache (DML)
Die Datenmanipulationssprache (kurz: DML für Data Manipulation Language) dient dazu,
Daten aus der Datenbank zu selektieren und anschliessend auszugeben oder zu verändern.
Auch hier werden wir die Sprache SQL verwenden.
5.2.1
Relationale Operationen
Im Kapitel 4 haben wir die Operationen der Relationenalgebra kennengelernt. In diesem
Abschnitt wollen wir zeigen, wie diese Operationen mit Hilfe von SQL ausgeführt werden
können. Alle Beispiele beziehen sich auf die im Anhang A definierte Lieferantendatenbank.
In SQL können alle relationalen Operationen mit Hilfe des SELECT Befehls durchgeführt
werden. Nachfolgend ist die allgemeine Form des Befehls angegeben.
<selection> ::= <query> [UNION|INTERSECT|EXCEPT <query>]...
[ORDER BY <attribut-list>]
<query>
::= SELECT [ALL|DISTINCT] <sel-item> [,<se-item>]...
5-2
<tableExpr> [[AS] <alias>] [,<tableExpr> [[AS] <alias>]]...
[WHERE <conditional-expression>]
[GROUP BY <attribut-list>
[HAVING <conditional-expression>]]
[<table-name>|<alias>].<attribut-name>
{<table-name>|<alias>}.*
*
<joinExpr>
<nonjoinExpr>
FROM
<sel-item> ::=
|
|
<tableExpr>::=
|
Der SELECT Befehl hat als Input eine Relation, welche in der FROM-Klausel angegeben wird
und als Resultat entsteht wieder eine Relation. Die Attribute des Resultats werden im SELECT
Teil angegeben. Der WHERE Teil dient als Filter.
5.2.1.1
Selektion
Der einfachste Fall besteht darin, alle Tupel einer Tabelle zu selektieren. Der folgende Befehl
selektiert alle Lieferanten.
SELECT
FROM
*
lieferant
Der * im SELECT Teil bedeutet, dass alle Attribute von lieferant auch im Resultat erscheinen sollen. Mit Hilfe der WHERE Klausel können nun auch Tupel selektiert werden, die
einer gegebenen Bedingung genügen. Der folgende Befehl selektiert alle Lieferanten mit dem
Ort ’Bern’.
SELECT
FROM
WHERE
*
lieferant
ort = ’Bern’
In der WHERE Klausel dürfen beliebieg viele Bedingungen angegeben werden, die mit den
logischen Operatoren AND, OR und NOT kombiniert werden können. Der folgende Befehl
selektiert alle Lieferanten aus Bern, die den Namen Meier haben.
SELECT
FROM
WHERE
5.2.1.2
*
lieferant
ort = ’Bern’ AND
name = ’Meier’
Projektion
Für die Projektion werden die gewünschten Felder explizit in der SELECT Klausel angegeben.
Der folgende Befehl selektiert die Namen aller Lieferanten.
5-3
SELECT
FROM
lieferant.name
lieferant
Bemerkung 5.3 [Mathematische Projektion] Dieser Befehl ist keine Projektion im Sinne
der Relationenalgebra, da das Resultat nicht unbedingt eine Relation ist. Falls mehrere Lieferanten denselben Namen besitzen, wird dieser Name auch mehrfach im Resultat vorkommen
(⇒ keine Relation). Um sicher zu sein, dass ein Tupel im Resultat nur einmal vorkommt,
muss das Keywort DISTINCT angegeben werden.
SELECT DISTINCT
FROM
lieferant.name
lieferant
Da in unserem Beispiel das Attribut name in der FROM-Klausel eindeutig ist, muss der Präfix
lieferant nicht unbedingt angegeben werden. Der folgende Befehl ist also auch legal.
SELECT DISTINCT
FROM
name
lieferant
Die Projektion kann auch mit einer Selektion verbunden werden. Mit dem folgenden Befehl
werden alle Lieferanten aus Bern selektiert. Auf das Resultat wird dann die Projektion auf
den Namen und die Postleitzahl angewendet.
SELECT DISTINCT
FROM
WHERE
5.2.1.3
name, plz
lieferant
ort = ’Bern’
Vereinigung
Die Vereinigung zweier Relationen R1 und R2 ist nur möglich, wenn beide Relationen dieselbe
Anzahl Attribute besitzen und die Datentypen der entsprechenden Attribute gleich (oder
kompatibel) sind. Der folgende Befehl selektioniert die Namen der Lieferanten und vereinigt
diese mit den Bezeichnungen der Produkte.
SELECT
FROM
name
lieferant
UNION
SELECT
FROM
bezeichnung
produkt
Wir können auch mehrere Vereinigungen hintereinander durchführen. Der folgende Befehl
selektiert alle Nummern in unserem System, die grösser als 4 sind.
5-4
SELECT
FROM
WHERE
l_nr
lieferant
l_nr > 4
UNION
SELECT
FROM
WHERE
p_nr
produkt
p_nr > 4
UNION
SELECT
FROM
WHERE
b_nr
bestellung
b_nr > 4
Bemerkung 5.4 [Mengentheoretische Vereinigung] Der UNION Befehl selektioniert
jedes Tupel genau einmal. Das heisst, Tupel mit gleichem Wert werden bis auf ein Exemplar
aus dem Resultat eliminiert. Dies ist ja auch korrekt, da das Resultat wieder eine Menge sein
soll. Will man die gleichen Tupel nicht eliminieren, so muss der Befehl UNION ALL verwendet
werden.
5.2.1.4
Durchschnitt und Differenz
In SQL/89 existieren diese beiden Operationen nicht direkt und sind erst ab SQL-92 verfügbar.
Auch für diese Befehle müssen die Relationen kompatibel sein. Das heisst, die Anzahl und der
Datentyp der Parameter muss gleich sein. Im nächsten Beipiel werden alle Strings selektioniert,
die sowohl als Namen eines Lieferanten, wie auch als Bezeichnung für ein Produkt vorkommen.
name
lieferant
SELECT
FROM
INTERSECT
SELECT
FROM
bezeichnung
produkt
Der Befehl für die Mengendifferenz heisst
5.2.1.5
EXCEPT.
Natürlicher Verbund in SQL-89
Die einzige Verbundoperation bis SQL-89 ist das kartesische Produkt. Um den natürlichen
Verbund zu erhalten, muss das kartesische Produkt mit einer Selection verbunden werden.
Als erstes wollen wir zeigen, wie man das kartesische Produkt zweier Relationen bilden kann.
Dazu werden in der FROM Klausel beide Relationen angegeben. Der folgende Befehl liefert eine
Relation mit allen möglichen Kombinationen von existierenden Lieferanten und Bestellungen.
SELECT
FROM
*
lieferant, bestellung
Das Resultat dieser Operation ist natürlich nicht sehr sinnvoll. Was wir wollen, sind nur
diejenigen Tupel, bei denen die Lieferantennummern übereinstimmen. Dieses Resultat kann
erzielt werden, indem noch eine Bedingung mit Hilfe der WHERE Klausel angegeben wird.
5-5
SELECT
FROM
WHERE
*
lieferant, bestellung
lieferant.l_nr = bestellung.l_nr
Nun werden nur diejenigen Tupel selektiert, bei denen die Lieferantennummer im Lieferant
und in der Bestellung übereinstimmen. Unschön ist noch, dass die Lieferantennummer im
Resultat zweimal vorkommt. Mit Hilfe der Projektion kann dies jedoch behoben werden. Der
folgende Befehl liefert eine Relation in der die Lieferantennummer nur einmal vorkommt.
SELECT
FROM
WHERE
lieferant.*, bestellung.b_nr, bestellung.bestelldatum, bestellung.lieferdatum
lieferant, bestellung
lieferant.l_nr = bestellung.l_nr
Auch der natürliche Verbund kann mit einer Selektion verbunden werden. Der folgende Befehl
eliminiert noch alle Tupel mit einem Bestelldatum, das kleiner ist als der 1. März 2005.
SELECT
FROM
WHERE
*
lieferant, bestellung
lieferant.l_nr = bestellung.l_nr
AND bestellung.lieferdatum >= ’1-mar-2005’
Ein natürlicher Verbund kann mit SQL über beliebig vielen Relationen durchgeführt werden.
Im nächsten Befehl wollen wir die Bestellungen mit ihren Posten und den entsprechenden
Produkten ausgeben.
SELECT
FROM
WHERE
5.2.1.6
lieferant.*, bestellung.bestelldatum, produkt.bezeichnung, b_p.menge
lieferant, bestellung, b_p, produkt
lieferant.l_nr = bestellung.l_nr AND
bestellung.b_nr = b_p.b_nr AND
b_p.p_nr = produkt.p_nr
Natürlicher Verbund in SQL-92
Wir wollen nun sehen, wie die Verbundoperationen ab SQL-92 direkt auf der
durchgeführt werden können. Die vollständige Syntax ist:
FROM-Klausel
<joinExpr> ::= <tableExpr> [NATURAL] [<jointype>] JOIN <tableExpr>
[ON <conditional-expression>|USING (<attribut-list>)]
<jointype> ::= INNER
| LEFT [OUTER]
| RIGHT [OUTER]
| FULL [OUTER]
| UNION
5-6
Hier noch einige Einschränkungen:
•
NATURAL
und
UNION
können nicht gleichzeitig angegeben werden.
• Falls entweder UNION oder NATURAL angegeben ist, so kann weder eine
USING-Klausel angegeben werden.
ON-
• Falls weder UNION noch NATURAL angegeben ist, so muss entweder eine
oder eine USING-Klausel angegeben werden.
• Falls kein <jointype> angegeben ist, wird einfach
Bemerkung 5.5 [Noiseword]
OUTER
INNER
noch eine
ON-Klausel
angenommen.
ist optional und ist ein “noiseword”.
NATURAL JOIN Das folgende Beispiel entspricht genau dem natürlichen Verbund aus
der relationalen Algebra. Das heisst, der Verbund wird über alle Attribute die gleich heissen
durchgeführt. Die “Verbundattribute” kommen im Resultat nur einmal vor. Das Beispiel listet
alle Lieferanten mit ihren Bestellungen aus.
SELECT
FROM
*
lieferant
NATURAL JOIN
bestellung
USING-Klausel Mit hilfe der USING-Klausel kann angegeben werden, über welche Attribut der Verbund gemacht werden soll. Dies kann nützlich sein, wenn nicht über alle gemeinsamen Attributte verbunden werden soll. Die in der USING-Klausel verwendeten Attribute
müssen in beiden Tabellen Vorhandensein.
SELECT
FROM
*
lieferant
JOIN
bestellung
USING(l_nr)
In diesem Fall sind beide Queries absolut äquivalent.
ON-Klausel Mit hilfe der ON-Klausel kann eine beliebige Bedingung für das Verbinden
der Tupel angegeben werden. Das heisst, logisch wird das Kartesische Produkt der beiden
Tabellen gebildet und anschliessend mit Hilfe der Bedingung in der ON-Klausel “gefiltert”.
Im nächsten Beispiel wollen wir nur die Lieferanten mit offenen Bestellungen.
SELECT
FROM
*
lieferant JOIN bestellung
ON lieferant.l_nr = bestellung.l_nr
lieferdatum IS NULL
AND
Im Unterschied zu den anderen Beispielen ist die Nummer des Lieferanten zwei mal im Resultat.
5-7
LEFT- bzw. RIGHT-JOIN Beim natürlichen Verbund tritt oft das Problem auf, dass
Tupel die keine Entsprechung in der anderen Relation besitzen, im Resultat nicht erscheinen.
Beispiel 5.1 [Left Join] Falls wir alle Lieferanten mit ihren offenen Bestellungen auslisten
wollen, erscheinen alle Lieferanten, die keine offenen Bestellungen haben, nicht in der Liste.
Mit Hilfe eines OUTER JOIN-Befehls können auch solche Lieferanten ausgelistet werden.
SELECT
FROM
lnr, bnr
lieferant
NATURAL LEFT JOIN
bestellung
In der Resultatrelation wird für jeden Lieferanten, der keine offene Bestellung besitzt, ein
Tupel erzeugt. Die Felder der Relation bestellung, die ja in diesem Fall nicht definiert sind,
werden auf NULL gesetzt.
Das obige Beispiel kann auch folgendermassen geschrieben werden
SELECT
FROM
lnr, bnr
bestellung
NATURAL RIGHT JOIN
lieferant
Will man sowohl RIGHT wie auch LEFT angeben so kann dies mit FULL angeben werden. In
userem Beispiel macht das keinen Sinn, da zu jeder Bestellung ein Lieferant existiert.
UNION JOIN Dieser Befehl kann man am besten mit dem folgenden SQL-Befehl erklären.
Falls A und B Tabellen sind (oder auch berechnete Tabellen), so gilt:
SELECT
*
FROM
A
UNION JOIN
B
entspricht:
SELECT
FROM
A.*,
A
NULL, NULL,
. . . ,NULL
UNION ALL
SELECT
NULL, NULL,
FROM
B
. . . ,NULL, B.*
Das heisst, das Relationenschema von A wird um alle Attribute von B ergänzt (dasselbe
gilt für B und A). Anschliessend werden in beiden Relationen die “fremden” Kolonnen mit
Nullwerten gefüllt. Am Schluss wird die Vereinigung der beiden neuen Relationen gebildet.
Bemerkung 5.6 [Semantische Interpretation] Die semantische Interpretation des UNION
JOIN-Befehls ist schwierig. Vor allem die Interpretation der vielen Nullwerte.
5-8
5.2.1.7
Subqueries in der FROM-Klausel
In der FROM-Klausel sind nicht nur Tabellen zugelassen sondern auch beliebiege Subqueries
(SELECT-Statements). Das Resultat eines SELECT-Statements wird also wieder als Tabelle
interpretiert und kann als solche weiterverwendet werden.
Das nächste beispiel selektiert alle Lieferanten mit den entsprechenden Bestellungen. Vom
Resultat selektieren wir dann alle offenen Bestellungen.
SELECT
FROM
WHERE
*
(SELECT *
FROM
lieferant NATURAL
b.lieferdatum IS NULL
JOIN
bestellung) b
Man beachte, dass der Subquery immer zwischen Runden Klammern stehen muss und immer
mit einen sogenannten Alias (siehe nächster Abschnitt) umbenannt werden muss
5.2.1.8
Umbenennung mit Hilfe von Alias
Im Kapitel 4 haben wir gesehen, dass die Umbenennungsoperation notwendig wird, wenn in
einem natürlichen Verbund dieselbe Relation mehr als einmal vorkommt. Im SELECT Befehl
kann dies geschehen, indem alle Attribute einer Relation mit Hilfe von einem Alias umbenannt werden. Im folgenden Beispiel werden alle Lieferanten selektioniert, bei denen in einer
Bestellung sowohl das Produkt ’1’ wie auch das Produkt ’2’ vorkommen.
SELECT DISTINCT
FROM
lieferant.*
((lieferant
bestellung)
b_p) JOIN b_p posten1
b_p.p_nr = 1 AND posten1.p_nr = 2
NATURAL JOIN
NATURAL JOIN
WHERE
USING
(b_nr)
In diesem Fall ist posten1 Alias für den entsprechenden Relationsnamen b_p.
Bemerkung 5.7 [Verwendung von Alias] Alias dürfen natürlich auch verwendet werden,
wenn dies nicht unbedingt nötig wäre, zum Beispiel um die Schreibarbeit zu reduzieren.
5.2.1.9
Umbenennug mit Hilfe der Select-Klausel
Wir können ein Attribut auch mit Hilfe der
Beispiel zeigt:
SELECT
FROM
SELECT-Klausel
umbenennen wie das folgende
l_nr AS l_nr1
lieferant
Im Resultat heisst das Attribut l_nr1. Das nächste Beispiel berechnet nun den natürlichen
Verbund mit den Bestellungen.
5-9
SELECT
FROM
5.2.2
*
(SELECT l_nr AS l_nr1
FROM
lieferant) l JOIN bestellung b
ON l.l_nr1 = b.l_nr
Operatoren und Funktionen
Wir haben gesehen, dass SQL alle Operationen der Relationenalgebra (wenigstens ab SQL-92)
zur Verfügung stellt. SQL erlaubt es aber auch, Operationen auf Attributwerten durchzuführen.
Insbesondere können arithmetische Operationen durchgeführt werden. Im folgenden Befehl
wird die Stückzahl eines Produktes aus der Menge und der Liefereinheit gebildet.
SELECT
FROM
l.l_nr, name, b.b_nr, bezeichnung, (menge * liefereinheit)
((lieferant l NATURAL JOIN bestellung b)
NATURAL JOIN b_p) NATURAL JOIN produkt
AS
Stueckzahl
In der resultierenden Relation haben wir also ein neues Attribut Stueckzahl definiert, das
angibt, wieviele Stücke eines Produktes bestellt sind.
Solche Operationen sind nicht nur auf Zahlen möglich, sondern auch auf anderen Datentypen.
Zum Beispiel können Strings mit dem ’||’ Operator konkateniert werden. Im nächsten Beispiel
wird in der Resultatrelation ein neues Attribut Kombination kreiert, das den Namen des
Lieferanten und die Bezeichnung des Produktes enthält.
SELECT
FROM
’Name: ’ || l.name || ’ Bez.: ’ || p.bezeichnung AS Kombination
((lieferant l NATURAL JOIN bestellung) NATURAL JOIN b_p)
NATURAL JOIN produkt p
Bemerkung 5.8 [Weitere Operatoren] Die zulässigen Funktionen und Operationen auf
einem gegebenen Datentyp können im entsprechenden Manual der verwendeten Datenbank
gefunden werden oder im SQL-92 Standard.
5.2.3
SQL Built-In Funktionen
Die Built-In Funktionen von SQL erweitern die Möglichkeiten der relationalen Algebra. Mit
den von Codd geforderten Operatoren ist eine einfache Anfrage wie “wieviele Lieferanten gibt
es?”nicht möglich. Dieser Mangel wird durch Built-In Funktionen aufgehoben. SQL kennt die
folgenden Funktionen, die auf einer Kolonne (ausser der Funktion COUNT(*)) einer Relation
angewendet werden und als Resultat einen Wert liefern. Die Relation kann eine Basisrelation
oder eine mit SQL konstruierte Relation sein.
COUNT(*)|COUNT(<attribut-name>)
Gibt die Anzahl Tupel in einer Relation zurück.
Falls ein Attributname angegeben ist, so werden nur die Tupel gezählt, für die der
entsprechende Attributwert nicht NULL ist.
SUM(<attribut-name>)
Berechnet die Summe des Attributs über alle Tupel.
5-10
AVG(<attribut-name>)
Berechnet den Durchschnitt des Attributs über alle Tupel.
MAX(<attribut-name>)
MIN(<attribut-name>)
Bestimmt den maximalen Wert des Attributs über alle Tupel.
Bestimmt den minimalen Wert des Attributs über alle Tupel.
Wir können nun mit dem folgenden Befehl die Anzahl Lieferanten bestimmen.
SELECT COUNT(*) AS
FROM
anzLieferanten
lieferant
Der nächste Befehl gibt die Anzahl Lieferanten in Bern zurück.
SELECT COUNT(*) AS
FROM
WHERE
anzLieferanten
lieferant
ort = ’Bern’
Das nächste Beispiel berechnet die seit dem 1. Januar 2005 bestellte Stückzahl für das Produkt
mit Nummer 1.
SELECT SUM(menge
FROM
WHERE
* liefereinheit) AS Stueckzahl
(bestellung b NATURAL JOIN b_p) NATURAL JOIN produkt p
p.p_nr = 1 AND b.bestelldatum >= ’1-jan-2005’
Das nächste Beispiel berechnet die durchnittliche Bestellmenge für das Produkt Nummer 4.
SELECT AVG(menge
FROM
WHERE
5.2.4
* liefereinheit)
(bestellung b NATURAL JOIN b_p)
p.p_nr = 4
NATURAL JOIN
produkt p
Bedingungen
Bis jetzt haben wir in der WHERE Kausel nur einfache Vergleiche zwischen Attributen oder
zwischen Attribute und Konstanten verwendet. Dies ist auch was in der Relationenalgebra für
die Selektion gefordert wird. In SQL können weit kompliziertere Bedingungen gestellt werden,
die in diesem Abschnitt erklärt sind.
5.2.4.1
Das Predikat
LIKE
Das Predikat LIKE erlaubt den Vergleich eines Feldes mit einem Muster. Die allgemeine Form
von like ist:
<like-clause> ::= <attribut-name> [NOT]
5-11
LIKE
<string> [ESCAPE <escape-char>]
Innerhalb von <string> bedeutet der Charakter ’%’, dass an dieser Stelle eine Sequenz von
beliebigen Charakters stehen kann (die Länge der Sequenz darf auch 0 sein). Der Charakter
’_’ bedeutet, dass an dieser Stelle ein beliebiges Zeichen stehen darf. Die anderen Zeichen im
String stehen für sich selbst. Mit der ESCAPE Klausel kann ein Escape-Character angegeben
werden, mit welchem die Bedeutung von ’%’ und ’_’ ausgeschaltet werden kann (der Defaultescapecharacter ist \). Im folgenden Befehl werden alle Lieferanten selektiert, deren Name
mit einem A beginnt.
SELECT
FROM
WHERE
*
lieferant
name LIKE ’A%’
Im nächsten Befehl werden alle Produkte gesucht, bei denen in der Bezeichnung das Zeichen
’%’ vorkommt.
SELECT
FROM
WHERE
5.2.4.2
*
produkt
bezeichnung
LIKE
’%$%%’
ESCAPE
’$’
Subqueries in der WHERE-Klausel
Ein Subquery ist ein SELECT Befehl, der in einem anderen SELECT Befehl verschachtelt ist.
Subqueries werden dazu verwendet, einen Wert oder eine Menge von Werten aufzubauen und
zu fragen, ob ein gegebenes Feld dem Wert entspricht oder in der Menge vorhanden ist. Der
folgende Befehl selektiert alle Lieferanten, mit dem gleichen Ort wie der Lieferant Nummer 1.
SELECT
FROM
WHERE
*
lieferant l
l.ort = (SELECT l1.ort
FROM
lieferant l1
WHERE l1.l_nr = 1)
Damit der Subquery mit einem Attribut (oder einer Konstanten) verglichen werden kann,
darf er nur einen einzelnen Wert erzeugen (und nicht eine Menge von Werten).
Bemerkung 5.9 [Verschachtelung] Subqueries können in beliebiger Tiefe verschachtelt
werden.
5.2.4.3
Das Prädikat
IN
Die allgemeine Syntax des
IN
Prädikats ist
<in-predikat> ::= {<attribut-name>|<konstante>} [NOT]
<liste>
::= (literal [,literal]...)
| (<subquery>)
5-12
IN
<liste>
Dieser Ausdruck ist wahr, falls der Wert des Attributs in der Liste vorkommt (bzw. nicht
vorkommt, wenn NOT IN angegeben wird). Der folgende Befehl selektiert alle Lieferanten mit
Ort Bern, Genf oder Thun.
SELECT
FROM
WHERE
*
lieferant l
l.ort IN (’Bern’, ’Genf’, ’Thun’)
Der nächste Befehl selektiert alle Lieferanten, bei denen das Produkt mit der Bezeichnung
“Luins” nach dem 1. Januar 2005 bestellt wurde.
SELECT
FROM
WHERE
*
lieferant l
’Luins’ IN (SELECT p.bezeichnung
FROM
bestellung b NATURAL JOIN b_p bp
NATURAL JOIN produkt p
WHERE l.l_nr = b.l_nr AND
b.bestelldatum >= ’1-jan-2005’)
Bemerkung 5.10 [Scope der Variablen] In diesem Beispiel sieht man auch, dass Variablen
des äusseren Queries im verschachtelten Query verwendet werden können. In unserem Beispiel
wird die Variable l.l_nr des äusseren Queries im verschachtelten Query wieder verwendet. Dies
bedeutet, dass der innere Query für jedes Tupel aus Lieferant einmal ausgeführt wird. Dabei
enthält l.l_nr die Nummer des aktuellen Lieferanten.
5.2.4.4
Die Prädikate
ANY
und
ALL
Diese beiden Prädikate haben die folgende allgemeine Form
<any-or-all> ::= {<attribut-name>|<konstante>}
{= | <> | < | > | <= | >= } {ANY |
<liste>
::= (literal [,literal]...)
| (<subquery>)
ALL}
<liste>
Diese Bedingung ist wahr, wenn der Vergleich für alle (ALL) bzw. für mindestens ein (ANY)
Element der Liste wahr ist. Der folgende Befehl selektiert alle Produkte, deren Lagermenge
grösser ist als die Lagermenge der Produkte mit Nummer kleiner oder gleich 3.
SELECT
FROM
WHERE
*
produkt p
p.lagermenge >
ALL (SELECT DISTINCT
FROM
WHERE
5-13
p1.lagermenge
produkt p1
p1.p_nr <= 3)
5.2.4.5
Das Prädikat
EXISTS
Das Prädikat EXISTS testet, ob in einer Tabelle mindestens ein Tupel vorhanden ist. Die werte
innerhalb des Tupels spielen dabei keine Rolle. Der folgende Befehl selektiert alle Lieferanten,
bei denen mindestens einmal das Produkt mit Nummer 2 bestellt wurde.
SELECT
FROM
WHERE
5.2.5
Die
*
lieferant l
EXISTS (SELECT *
FROM
bestellung b NATURAL JOIN b_p bp
WHERE l.l_nr = b.l_nr AND bp.p_nr = 2)
ORDER BY
Klausel
In einer Relation ist für die Tupel keine Ordnung vorgegeben. Man kann nicht sagen, in
welcher Reihenfolge die Resultattupel eines SELECT Befehls ausgegeben werden. Dies führt
oft zu nicht lesbaren Resultaten. Mit Hilfe der ORDER BY Klausel kann die gewünschte
Reihenfolge erzwungen werden.
<orderby-klausel> ::=
ORDER BY
<attribut> [ASC|DESC], [<attribut> [ASC|DESC]]. . .
Der folgende Befehl selektiert die Lieferanten mit den Bestellungen und Posten, sortiert nach
Lieferantenname, Bestelldatum (absteigend), Bestellnummer und Produktnummer.
l.name, b.b_nr, b.bestelldatum, bp.p_nr, bp.menge
FROM lieferant l NATURAL JOIN bestellung b NATURAL JOIN b_p bp
ORDER BY
l.name,b.bestelldatum DESC,b.b_nr,bp.p_nr
SELECT
5.2.6
Die GROUP BY Klausel
Die GROUP BY Klausel fasst Tupel, die in den angegebenen Attributen übereinstimmen zu
einer Gruppe zusammen.
<groupby-klausel> ::=
GROUP BY
<attribut-list>
In der SELECT Klausel dürfen dann nur Felder angegeben werden, die für die ganze Gruppe
denselben Wert haben. Dies gilt natürlich für alle Felder, die in der GROUP BY Klausel
angegeben sind und ferner für das Resultat von Built-In Funktionen. Der folgende Befehl
listet wieviele Lieferanten in jedem Ort ansässig sind.
SELECT
FROM
GROUP BY
l.ort, COUNT(*)
lieferant l
l.ort
AS
anzahl
5-14
Der nächste Befehl listet die bestellte Stückzahl von jedem Produkt seit dem 1. Januar 2005.
SELECT
FROM
WHERE
GROUP BY
5.2.7
p.bezeichnung, SUM(bp.menge * p.liefereinheit) AS Stueckzahl
(bestellung b NATURAL JOIN b_p bp) NATURAL JOIN produkt p
b.bestelldatum >= ’1-jan-2005’
p.p_nr, p.bezeichnung
Die HAVING Klausel
Die HAVING Klausel ist die Selektionsklausel für Gruppen. Das heisst, HAVING dient dazu, nur
gewisse Gruppen auszuwählen. Die HAVING Klausel kann daher nur in Verbindung mit einer
GROUP BY Klausel angegeben werden. Der nächste Befehl selektiert nur Orte mit mindestens
drei Lieferanten.
SELECT
FROM
GROUP BY
HAVING
5.2.8
l.ort, COUNT(*) AS anzahl
lieferant l
l.ort
COUNT(*) >= 3
Null-Werte
Null-Werte repräsentieren undefinierte oder unbekannte Werte. Der Wert NULL ist nicht dasselbe wie 0 oder der Leerstring.
Null-Werte können nicht mit anderen Werten verglichen werden. Der Vergleich von NULL mit
einem anderen Wert ist immer falsch. Auch der Vergleich von zwei Null-Werten ist immer
falsch.
Mit dem Prädikat IS [NOT] NULL ist es hingegen möglich, abzufragen, ob der Wert eines Attributes NULL ist oder nicht. Der folgende Befehl listet alle noch nicht ausgelieferten Bestellungen auf. Das heisst, Bestellungen in denen das Lieferdatum den Wert NULL hat.
SELECT
FROM
WHERE
*
bestellung
lieferdatum
IS NULL
Der nächste Befehl listet alle Bestellungen, die vor dem 1. Januar 2005 ausgeliefert wurden.
SELECT
FROM
WHERE
*
bestellung
lieferdatum IS NOT NULL AND
lieferdatum < ’1-jan-2005’
Bemerkung 5.11 [Built-In und Nullwerte] Bei den Built-In Funktionen SUM, AVG,
und MIN werden Tupel, die im entsprechenden Feld einen Null-Wert enthalten einfach
übersprungen.
MAX
5-15
5.3
Views
In einer relationalen Datenbank unterscheiden wir zwischen Basisrelationen und Views (oder
Sichten). Die Basisrelationen sind alle mit dem Befehl CREATE TABLE erzeugten Relationen
der Datenbank (physische Relationen). Eine View hingegen ist eine virtuelle Tabelle, die
aufgrund eines SQL SELECT-Befehls aus Basisrelationen und anderen Views zusammengestellt
wird. Eine View kann also Informationen aus einer oder auch mehreren Basistabellen oder
Views enthalten. Views haben die folgenden Eigenschaften:
• In einer View werden keine Daten physisch gespeichert. Bei einer Anfrage auf einer
View werden die Informationen aus den Basisrelationen zusammengebaut, so dass die
Informationen auch immer aktuell sind.
• Auf einer View sind dieselben Anfrage-Operationen erlaubt wie auf einer Basisrelation.
• Da in einer View keine Daten physisch gespeichert sind, müssen Update-Operationen
auf Views in Update-Operationen der zugrundeliegenden Basisrelationen übersetzt werden. Eine solche Übersetzung ist aber nicht in jedem Fall möglich, so dass UpdateOperationen nur auf gewissen speziellen Views möglich sind (siehe Abschnitt 5.4.2)
5.3.1
Definition von Views
Views werden in SQL mit dem
<view-definition> ::=
CREATE VIEW
Befehl definiert.
<view-name>
[(<attribut-name> [, <attribut-name>]...)]
CREATE VIEW
AS
<selection>
[WITH CHECK OPTION]
<selection> ist ein beliebiges Select-Statement und ist im Skript Datenbanken Teil I beschrieben.
Bemerkung 5.12 [Create View]
• Der CREATE VIEW Befehl selektiert selbst keine Daten. Die Tupel einer View werden erst
dann zusammengebaut, wenn ein Befehl auf dieser View gestartet wird. Dies garantiert,
dass die Daten einer View immer aktuell sind.
• Eine View definiert, als Resultat eines SELECT Befehls, wieder eine Relation . Vom
Standpunkt des Benutzers gibt es keinen Unterschied zwischen einer Basisrelation und
einer View (ausser bei Update-Operationen). Da eine View selbst eine Relation ist, darf
bei der Definition keine ORDER BY Klausel verwendet werden.
Eine View kann mit dem
<delete-view> ::=
DROP VIEW
DROP VIEW
Befehl wieder gelöscht werden.
<view-name> {RESTRICT |
5-16
CASCADE}
Falls RESTRICT angegeben wird und die View in einer anderen View-Definition verwendet
wird, so wird die Operation nicht durchgeführt.
Beispiel 5.2 [View Definitionen] Wir wollen nun einige Views auf unserer Beispiel-Datenbank
definieren.
1. In einer View sollen alle Produkte, mit einer Lagermenge kleiner als 10 vorkommen.
CREATE VIEW
SELECT
FROM
WHERE
kleine_lagermenge
*
produkt
lagermenge < 10
2. Im folgenden Beispiel wird in der View das Attribut Liefereinheit nicht eingeschlossen.
Ferner wird das Attribut Lagermenge auf menge umbenannt.
kleine_lagermenge
(p_nr, bezeichnung, menge) AS
p_nr, bezeichnung, lagermenge
produkt
lagermenge < 10
CREATE VIEW
SELECT
FROM
WHERE
3. Eine View kann aber auch über mehrere Basisrelationen definiert sein und auch berechnete Attribute enthalten. Im nächsten Beispiel wird eine View kreiert, die zu jedem
Produkt die bestellten (aber noch nicht gelieferten) Stückzahlen enthält.
offene_bestellungen
(p_nr, bezeichnung, stueckzahl) AS
p.p_nr, p.bezeichnung,
SUM(bp.menge * p.liefereinheit)
produkt p natural join bestellung b natural join bp.b_p
b.lieferdatum IS NULL
p.p_nr, p.bezeichnung
CREATE VIEW
SELECT
FROM
WHERE
GROUP BY
5.3.2
5.3.2.1
Operationen auf Views
Relationale Operationen auf Views
Auf Views können wie schon gesagt beliebige relationale Operationen ausgeführt werden. Es
bestehen also zwischen einer View und einer Basisrelation von diesem Standpunkt aus keine
Unterschiede.
Beispiel 5.3 [View Stückzahl] Wir möchten von allen Produkten, für die offene Bestellungen existieren, die bestellten Stückzahlen sowie die aktuelle Lagermenge ausgeben. Dazu
können wir die im Beispiel 5.2 definierte View offene_bestellungen verwenden.
SELECT
FROM
ob.p_nr, ob.bezeichnung, ob.stueckzahl, p.lagermenge
offene_bestellungen ob natural join produkt p
5-17
5.3.2.2
Update Operationen auf Views
Bei Anfragen verhalten sich Views genau gleich wie Basisrelationen. Die Frage ist nun, wann
Update-Operationen auf Views überhaupt möglich sind. Wir wollen zuerst die Fälle betrachten, in denen im allgemeinen keine eindeutige Übersetzung der Update-Befehle auf die
zugrundeliegenden Basisrelationen möglich ist.
1. Die View enthält Felder aus mehr als einer Basisrelation. In diesem Fall ist es nicht
immer möglich, eine eindeutige Übersetzung der Update-Befehle auf die entsprechenden
Basisrelationen zu finden, wie das folgende Beispiel zeigt.
Die folgende View enthält alle Lieferanten mit ihren Bestellungen.
l_bestellung
(l_nr, name, ort, plz, b_nr, bestelldatum, lieferdatum) AS
l.l_nr, l.name, l.ort, l.plz, b.b_nr, b.bestelldatum, b.lieferdatum
lieferant l NATURAL JOIN bestellung b
CREATE VIEW
SELECT
FROM
Wir betrachten als erstes den folgenden
UPDATE
SET
WHERE
UPDATE-Befehl:
l_bestellung
ort = ’Bern’
l_nr = 1 AND
bestelldatum = ’10-jun-2005’
Das Problem ist hier, dass der Ort für einen Teil der Bestellungen verändert werden
muss und für einen andern Teil nicht. Dies ist aber nicht möglich, da der Lieferant nur
einmal physisch in der Datenbank gespeichert ist. Dies kommt daher, dass ind der SET
Klausel und in der WHERE Klausel Attribute aus verschiedenen Tabellen vorkommen.
Falls dies nicht der Fall ist, so ist eine solcher Update kein Problem wie das folgende
Beispiel zeigt.
UPDATE
SET
WHERE
Dieses
l_bestellung
ort = ’Bern’
name = ’Fierz’
UPDATE
UPDATE
SET
WHERE
Statement entspricht genau folgendem Befehl.
lieferant
ort = ’Bern’
name = ’Fierz’
Bemerkung 5.13 [Update erlauben] Man kann sich aber auch auf den Standpunkt
stellen, dass die Änderungen für alle selektierten Tupel der View auf die entsprechenden
Tupel der zugrundeliegenden Basisrelationen ausgeführt werden. Als Beispiel betrachten
wir den folgenden Update-Befehl.
UPDATE
SET
l_bestellung
ort = ’Bern’,
bestelldatum = ’5-nov-2007’
5-18
WHERE
l_nr <= 3 AND
lieferdatum is NULL
In diesem Fall werden alle Lieferanten mit Nummer kleiner als 3 und alle Bestellungen
der entsprechenden Lieferanten mit einem Nullwert im Lieferdatum geändert.
Wir betrachten noch den folgenden
DELETE FROM
WHERE
DELETE-Befehl:
l_bestellung
l_nr = 1 AND
bestelldatum = ’1-nov-2005’
Die Schwierigkeit in diesem Fall ist ähnlich wie beim UPDATE-Befehl. Auch hier müsste
der Lieferant für einen Teil der Bestellungen gelöscht werden und für einen anderen Teil
nicht.
Auch beim Einfügen eines Tupels entstehen ähnliche Probleme. In einigen Datenbanksysteme sind Update-Operationen auf Views, die aus mehreren Basistabellen bestehen nicht
erlaubt. Bemerkung 5.14 [Update Views] Gewisse Datenbanksysteme (zum Beispiel
Sybase) erlauben die Operation UPDATE auf Views mit mehreren Basisrelationen (siehe
Bemerkung 5.13). Die Datenbank Mysql erlaubt UPDATE Operationen nur dann, wenn
alle veränderten Attribute aus derselben Basisrelation stammen. Die Operationen INSERT und DELETE sind aber sowohl in Sybase wie auch Mysql auf Views mit JOIN’s
generell nicht zugelassen.
2. Falls die View eine DISTINCT Klausel enthält, so sind Updateoperationen nicht erlaubt.
3. Eine View mit UNION Operationen ist nicht updatable. Der Grund ist, dass in einer
solchen View die Tupel aus verschiedenen Basistabellen stammen können.
4. Die View enthält ein Feld, das aus einem Ausdruck gebildet ist. Ein solches Feld existiert
physisch nirgends und kann daher weder verändert noch eingefügt werden.
5. Die View enthält eine GROUP BY-Clausel und Felder, die mit einer SQL Built-In Funktion wie SUM, AVG usw. berechnet werden. Das Verändern oder Einfügen solcher Felder
macht natürlich keinen Sinn. Die Attributte der GROUP BY Klausel könnten eventuell
verändert werden. In allen Datenbanken und im Standard sind Updateoperationen auf
Views mit einer GROUP BY Klausel nicht erlaubt.
6. Sind NOT NULL Attribute ohne DEFAULT der zugrundeliegenden Basisrelation in der
View “ausgeblendet”, so ist das Einfügen von Tupel in dieser View nicht möglich.
7. Sind Attribute des Primärschlüssels der zugrundeliegenden Basisrelation in der View
“ausgeblendet”, so ist das Einfügen von Tupel in dieser View nicht möglich.
Falls die drei folgenden Bedingungen erfüllt sind, so kann eine View ohne Bedenken wie eine
Basisrelation verändert werden.
1. Die View enthält nur Attribute aus einer einzigen Basisrelation.
2. Der Primärschlüssel der Basisrelation ist vollständig in der View enthalten.
3. Alle Attribute mit der
NOT NULL
Klausel sind in der View vorhanden.
5-19
Dieser Spezialfall ist wichtig, falls der Zugriff auf einzelne Tupel mit Hilfe von Views geregelt
wird (siehe Kapitel 12).
Bemerkung 5.15 [Instead of Trigger] Gewisse Datenbanken kennen sogenannte INSTEAD
OF {INSERT|UPDATE|DELETE} TRIGGER. Mit solchen Triggern ist es möglich auf allen Views
Updateoperationen durchzuführen.
INSTEAD OF Trigger können sowohl für Basistabellen wie auch für Views definiert werden
und ersetzen die entsprechende Update Operation vollständig. Somit kann die Übersetzung
der Updateoperation einer View auf Basistabellen vom Benutzer vorgenommen werden.
Als Beispiel betrachten wir die folgende View mit einer GROUP BY Klausel. Diese View ist
nicht updatable aber mit einem INSTEAD OF Trigger können wir erreichen, dass die Attribute
des Lieferanten geändert werden können.
anzbestellungen
(l_nr, name, ort, anzahl) AS
l.l_nr, l.name, l.ort, COUNT(*)
lieferant l NATURAL JOIN bestellung b
l.l_nr, l.name, l.ort
CREATE VIEW
SELECT
FROM
GROUP BY
anzbest
anzbestellungen
OLD AS alt NEW AS neu
CREATE TRIGGER
INSTEAD OF UPDATE ON
REFERENCING
FOR EACH ROW
BEGIN ATOMIC
DECLARE
myexception
EXCEPTION FOR SQLSTATE
(alt.anzahl != neu.anzahl)
SIGNAL myexception;
END IF;
IF
IF
’99001’;
THEN
(alt.name != neu.name OR alt.ort != neu.ort) THEN
UPDATE lieferant SET name = neu.name, ort = neu.ort
WHERE l_nr = alt.l_nr;
END IF;
END
5.3.2.3
Die
WITH CHECK OPTION-Klausel
Als letztes wollen wir noch die Bedeutung der WITH CHECK OPTION-Klausel besprechen.
Diese Klausel macht nur einen Sinn, wenn die View verändert werden kan. Wenn Attributwerte
in einem Tupel der View verändert werden, ist es möglich, dass das Tupel anschliessend nicht
mehr zur View gehört. Ist die View mit der WITH CHECK OPTION definiert, so sind solche
Updates nicht erlaubt.
Beispiel 5.4 [With check option] Wir betrachten folgende View in unserer Beispieldatenbank.
CREATE VIEW
SELECT
kleine_lagermenge
*
5-20
FROM
WHERE
produkt
lagermenge < 10
Wir nehmen nun an, dass das folgende Tupel in der Relation Produkt vorhanden ist.
(1, Chablis 2005, 12, 8)
Dieses Tupel gehört klar auch zur View kleine_Lagermenge, da das Attribut Lagermenge
kleiner als 10 ist. Der folgende UPDATE-Befehl ist zulässig, obwohl das Tupel dann nicht
mehr zur View gehört.
UPDATE
SET
WHERE
kleine_lagermenge
lagermenge = 20
p_nr = 1
Wir betrachten nun die folgende View mit der
WITH CHECK OPTION-Klausel.
grosse_lagermenge
*
produkt
lagermenge >= 10
CREATE VIEW
SELECT
FROM
WHERE
WITH CHECK OPTION
Das folgende Tupel ist in der Relation produkt gespeichert.
(11, Chardonay 2004, 1, 33)
Das Tupel gehört auch zur View grosse_lagemenge, da das Attribut Lagermenge den Wert 33
besitzt. Weil die View mit der WITH CHECK OPTION-Klausel definiert ist, wird der folgende
UPDATE-Befehl von der Datenbank zurückgewiesen, weil das resultierende Tupel anschliessend
nicht mehr zur View gehört.
UPDATE
SET
WHERE
5.3.3
grosse_lagermenge
lagermenge = 7
p_nr = 11
Verwendung von Views
Views haben einige Vorteile, die wir in diesem Abschnitt noch näher betrachten wollen.
5.3.3.1
Verschiedene Sichten auf die Daten
Views sind ein wichtiger Bestandteil der 3-Schemen-Architektur (siehe Datenbanken Teil I).
Mit Hilfe von Views können für verschiedene Benutzer verschiedene Sichten auf die Daten
definiert werden, obschon die zugrundeliegenden Daten für alle Benutzer dieselben sind. Jeder
Benutzer kann mit Hilfe von Views genau auf die Informationen zugreifen, die für ihn relevant
sind und andere Informationen “ausblenden”.
5-21
5.3.3.2
Vereinfachung der Abfragen
Komplizierte und häufig verwendete Join-Operationen können in der Selektions-Klausel der
View verpackt werden. Ein Beispiel dafür ist die View offene_bestellungen aus dem Beispiel
5.2. Der Benutzer kann mit Hilfe dieser View ohne komplizierte Operationen sofort für beliebige Produkte die bestellten Stückzahlen abfragen.
5.3.3.3
Datenschutz
Daten, die mit Hilfe einer View “ausgeblendet” werden, sind für den Benutzer der View nicht
sichtbar und können auch nicht verändert werden. Mit Views ist es also möglich, Datenschutz
auf Feld sowie auf Tupelebene einzuführen. Wir werden diese Möglichkeiten im Kapitel über
Datenschutz (siehe Kapitel 12) näher betrachten.
5.3.3.4
Logische Datenunabhängigkeit
Zu einem Teil können die Applikationen gegenüber Änderungen im Datenbankschema (konzeptionelle oder logische Ebene der 3-Ebenen-Architektur) mit Hilfe von Views geschützt werden.
Dies wollen wir mit Hilfe eines Beispiels darstellen.
Beispiel 5.5 [Logische Unabhängigkeit] In unserer Beispieldatenbank wird die Tabelle
lieferant in die zwei neuen Tabellen lieferant_name und ort aufgeteilt. Das neue Schema sieht
also folgendermassen aus (dabei wird angenommen, dass die Postleitzahl den Ort eindeutig
bestimmt).
CREATE TABLE
l_nr
name
plz
lieferant_name (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
CHAR(8) NOT NULL REFERENCES
ort(plz),
)
CREATE TABLE
plz
ort
ort (
CHAR(8) PRIMARY KEY,
VARCHAR(30) NOT NULL,
)
Nun müssten alle Applikationen, die auf Lieferanten zugreifen, geändert werden. Durch die
Definition der folgenden View lieferant kann dies zu einem grossen Teil verhindert werden.
lieferant
(l_nr, name, ort, plz) AS
ln.l_nr, ln.name, ln.plz, o.ort
lieferanten_name ln, ort o
ln.plz = o.plz
CREATE VIEW
SELECT
FROM
WHERE
Alle Programme, die Lieferantendaten nur lesen, funktionieren jetzt unverändert weiter. Leider
müssen Programme, die Lieferantendaten schreiben angepasst werden, da die View “lieferant”
im allgemeinen nicht verändert werden kann.
5-22
5.4
Update und Delete Operationen
Bis jetzt haben wir nur Anfragen an die Datenbank mit Hilfe des SELECT Statements betrachtet. SQL bietet neben den Anfragen auch die Möglichkeit, Daten in die Datenbank einzufügen (INSERT), Daten zu verändern (UPDATE) und Daten zu löschen (DELETE). Wir wollen
diese drei Operationen nun näher betrachten.
Bemerkung 5.16 [Update nur auf einer Tabelle] Eine Update-Operation kann nur auf
einer Tabelle erfolgen. Sind bei einer Änderung mehrere Tabellen der Datenbank beteiligt,
so muss für jede Tabelle ein eigenes Update-Statement vorhanden sein. Die verschiedenen
Update-Operationen können aber in einer Transaktion zusammengefasst werden, so dass die
verschiedenen Updates von aussen gesehen wie eine einzige (atomare) Operation aussehen.
5.4.1
INSERT
In einer Tabelle können neue Tupel eingefügt werden, indem einfach eine Liste von Konstanten für die entsprechenden Attributwerte angegeben wird. Die Werte der Attributwerte
eines neuen Tupels können aber auch mit Hilfe eines SELECT-Befehls generiert werden. Die
allgemeine Syntax für den INSERT-Befehl lautet
<insert> ::=
<table>
[(<attribut-list>)]
<source>
<source> ::= VALUES (<literal-list>)
| SELECT <attribut-list> FROM . . . [WHERE . . . ] usw.
INSERT INTO
Die Attribute in der INTO und der SELECT Klauseln müssen in Anzahl und Datentyp übereinstimmen. Wird bei der INTO-Klausel keine Attributliste angegeben, so sind alle Attribute
des Relationenschemas gemeint. Die Reihenfolge ist durch die Reihenfolge der Definition der
Attribute im entsprechenden Relationenschema gegeben.
Beispiel 5.6 [Einfügen] Das nächste Beispiel fügt einen neuen Lieferanten in der Datenbank
ein.
lieferant
(l_nr, name, ort, plz)
VALUES (27, ’Glanz’, ’Genf’, ’1003’)
INSERT INTO
5.4.2
UPDATE
Die Update-Operation dient dazu, bestehende Tupel in der Datenbank zu verändern. Die
allgemeine Syntax lautet:
<update>
::=
<table-name>
<assignment-list>
[WHERE <conditional-expression>]
<assignment-list> ::= <attribut> = <value> [,<attribut> = <value>]. . .
<value>
::= literal
UPTATE
SET
5-23
| attribut-name
| (<subselect>)
Alle Tupel in der Tabelle, die der angegebenen Bedingung genügen, werden gemäss der SETKlausel verändert. Der <subselect> darf natürlich nur einen Wert des richtigen Datentyps
liefern.
Beispiel 5.7 [Ändern] Der nachfolgende UPDATE-Befehl ändert für alle Bestellungen des
Lieferanten mit Nummer 1 und mit Bestelldatum ’10-jun-2005’ das Datum auf den ’12-jun2005’.
UPDATE
SET
WHERE
bestellung
bestelldatum = ’12-jun-2005’
bestelldatum = ’10-jun-2005’ AND l_nr = 1
Beispiel 5.8 [Ändern mit Subselect] Im nächsten Beispiel wollen wir die Lagermenge des
produkts mit Nummer 1 auf die Summe aller Bestellposten mit Produktnummer 1 setzen.
UPDATE
SET
produkt
lagermenge = (SELECT
FROM
WHERE
WHERE
5.4.3
SUM(menge)
b_p
p_nr = 1) * liefereinheit
p_nr = 1
DELETE
Die Delete-Operation dient dazu, Tupel aus einer Tabelle zu löschen. Die Tupel können wieder
mit einer WHERE-Klausel ausgewählt werden. Die allgemeine Syntax lautet:
<delete> ::=
DELETE FROM <tablename>
[WHERE <conditional-expression>]
Beispiel 5.9 [Löschen] Der nächste Befehl löscht den Lieferanten mit Nummer 12 aus der
Datenbank.
DELETE
FROM
WHERE
lieferant
l_nr = 12
Bemerkung 5.17 [Löschen und Fremdschlüssel] Der obige Befehl wird von der Datenbank nur dann durchgeführt, wenn für den entsprechenden Lieferanten keine Bestellungen
mehr existieren.
5-24
5.5
5.5.1
Übungen
SQL-Queries
sql.subsec.sqlqueries
Um diese Aufgaben zu lösen können Sie wieder das Programm sqlframe benutzen, das Sie
schon für die Übungen zur relationalen Algebra verwendet haben.
Aufgabe 5.1 [SQL-Queries Personen-Beispiel] Schreiben Sie SQL-Queries, welche die
folgenden Aufgaben lösen
a) Bestimmen des Namens und des Vornamens aller Personen, die in Bern wohnen und
nach dem 1-jan-1950 geboren wurden.
b) Bestimmen des Namens aller Personen, die Theater als Hobby betreiben.
c) Bestimmen des Namens der Personen, die sowohl Golf wie Theater als Hobby betreiben.
d) Bestimmen des Namens der Personen, die Klavierspielen und Theater als Hobby haben
und keine weiteren Hobbies betreiben.
Aufgabe 5.2 [SQL-Queries Lieferanten-Beispiel] Alle folgenden Übungen beziehen sich
auf die im Script beschriebene Lieferanten-Produkt-Bestellung-Datenbank.
a) Selektieren Sie alle Bestellungen mit Name und Nummer des Lieferanten, sowie das
Bestelldatum sortiert nach Name und Bestelldatum.
b) Ergänzen Sie die vorhergehende Abfrage so, dass nur die schon gelieferten Bestellungen
erscheinen und listen Sie zusätzlich das Lieferdatum aus.
c) Erstellen Sie eine Liste aller Lieferanten mit Nummer, Name und Anzahl Bestellungen,
sortiert nach Anzahl Bestellungen. Lieferanten ohne Bestellungen sollen auch in der
Liste erscheinen. Benutzen Sie dabei den LEFT OUTER JOIN Befehl nicht.
d) Erstellen Sie eine Liste aller bestellten und noch nicht gelieferten Produkte mit ProduktNummer, Produktbezeichnung und die Anzahl bestellter Einheiten dieses Produkts. Die
Liste soll nach Produktnummer sortiert sein.
e) Erstellen Sie eine Liste aller Lieferanten, für welche eine Bestellung existiert wo sowohl
das Produkt mit Nummer 1 wie auch das Produkt mit Nummer 2 vorkommen.
f) Erstellen Sie eine Liste aller Lieferanten, bei denen sowohl das Produkt Nummer 1 wie
auch das Produkt Nummer 2 bestellt wurden.
g) Suchen Sie die Lieferanten, bei denen mindestens alle Produkte bestellt wurden, die auch
beim Lieferant mit Nummer 5 bestellt wurden.
h) Ergänzen Sie g) so, dass nur Lieferanten erscheinen, bei denen genau dieselben Produkte
bestellt wurden.
5-25
5.5.2
Aufgaben zu Views
sql.subsec.views
Aufgabe 5.3 [Veschiedene Sichten auf die Daten] Betrachten Sie für diese Aufgabe die
Lieferanten-Datenbank.
In einer Firma werden die Lieferanten mit plz 1000 bis 4000 und die Lieferanten mit plz 4001
bis 9999 zusammen mit den entsprechenden Bestellungen von zwei verschiedenen Sachbeartbeitern verwaltet.
a) Stellen Sie in der Datenbank Views zur Verfügung, die diese Situation behandeln.
b) Wieso ist es in diesem Fall wichtig, dass die Views Update-Operationen (INSERT, UPDATE und DELETE) zulassen?
Ist dies mit der Datenbank Mysql möglich?
Aufgabe 5.4 [Updatable Views] Gegeben sei die folgende View:
bestellungen
(l_nr, name, ort, b_nr, datum, anzahl) AS
l.l_nr, l.name, l.ort, b.b_nr, b.bestelldatum,
lieferant l, bestellung b, b_p bp
l.l_nr = b.l_nr AND
b.b_nr = bp.b_nr
l.l_nr, l.name, l.ort, b.b_nr, b.bestelldatum
CREATE VIEW
SELECT
FROM
WHERE
GROUP BY
COUNT(*)
a) Entscheiden Sie, welche der folgenden SQL-Befehle auf der gegebenen View prinzipiell
ausgeführt werden können und welche nicht. Begründen Sie Ihre Antworten (Mysql lässt
auf dieser View keine Updates zu).
1.
bestellungen SET name = ’Fierz’
l_nr = 1
UPDATE bestellungen SET name = ’Maurer’
WHERE anzahl < 7
DELETE FROM bestellungen
WHERE name = ’fierz’
INSERT INTO bestellungen
VALUES (22, ’Gloor’, ’Basel’, 33, ’2004-01-0’)
UPDATE bestellungen SET datum = ’2005-09-10’
WHERE datum = ’2006-09-09’
UPDATE bestellungen SET anzahl = 9
WHERE anzahl = 8
UPDATE
WHERE
2.
3.
4.
5.
6.
b) Schreiben Sie einen SQL-Befehl, der mit Hilfe der obigen View bestellungen zu jedem
Lieferant (l_nr, name) die durchschniliche Anzahl Posten pro Bestellung ausgibt.
5-26
Kapitel 6
Normalisierung (Dekomposition)
Die Normalisierung ist eine Zerlegung von Relationen nach bestimmten Vorschriften mit den
folgenden Zielen.
• Die gesamte Information in der Datenbank muss redundanzfrei sein. D.h., jedes Faktum,
jeder Wert eines Attributs ist genau an einem Ort festgehalten. Man spricht vom “one
fact in one place”-Prinzip.
• Durch Insert-, Update- und Delete-Operationen sollen keine Widersprüche in der Datenbank auftreten (Update-Anomalien).
• Die Abfrage und Bearbeitung der Daten soll einfach und sicher sein. Normalisierte
Tabellen bilden die Grundlage zu einer syntaktisch einfachen Datenmanipulationssprache
(SQL).
Das folgende Beispiel wird zur Illustration der Normalisierung in den folgenden Abschnitten
verwendet.
Beispiel 6.1 [Unnormalisierte Tabelle] Eine Firma möchte die folgenden Daten über
jeden Lieferanten speichern: Die Nummer (L)NR, der Wohnort (O)RT und die Entfernung
zu diesem Ort (D)IST, für jedes gelieferte Teil dessen Bezeichnung (T)BEZ und die bisher
gelieferte Anzahl (A)NZ. Nachfolgend die (unnormalisierte) Tabelle.
(L)NR
1
(O)RT
London
(D)IST
1000
2
Paris
570
3
4
Paris
Berlin
570
960
6-1
(T)BEZ
T1
T2
T3
T4
T1
T2
T2
T2
T2
T4
T5
(A)NZ
300
200
400
200
450
300
200
150
150
750
350
6.1
Erste Normalform 1NF
In unserem Beispiel können die Attribute BEZ und ANZ als ein Attribut TEIL angesehen
werden, dessen Werte selbst wieder Relationen sind.
Beispiel 6.2 [Tupel als Attributwerte] In unserem Beispiel würden etwa die Tupelmengen
{(T1, 450), (T2, 300)}
{(T2, 150), (T4, 750), (T5, 350)}
zur Domäne des Attributs TEIL gehören.
Eine Relation, die Attribute mit relationwertigen Domänen enthält, heisst unnormalisiert.
Im relationalen Modell sind aber relationenwertige Domänen nicht erlaubt. Eine solche Relation muss in erster Normalform transformiert werden.
Definition 6.1 [Erste Normalform 1NF] Eine Relation R ist in erster Normalform (1NF) genau dann, wenn die Domänen aller Attribute nur atomare Werte
enthalten.
Um eine Relation in die erste Normalform zu bringen, müssen alle existierenden Tupel sooft
kopiert und die mehrfach belegten Attribute auf die Kopien aufgeteilt werden, dass in jedem
Feld nur noch ein Wert steht. Im Beispiel 6.3 ist die Relation in erster Normalform angegeben.
Beispiel 6.3 [Tabelle in 1NF]
(L)NR
1
1
1
1
2
2
3
4
4
4
4
(O)RT
London
London
London
London
Paris
Paris
Paris
Berlin
Berlin
Berlin
Berlin
(D)IST
1000
1000
1000
1000
570
570
570
960
960
960
960
(T)BEZ
T1
T2
T3
T4
T1
T2
T2
T2
T2
T4
T5
(A)NZ
300
200
400
200
450
300
200
150
150
750
350
Bemerkung 6.1 [Umwandlung in die 1NF] Die Umwandlung einer nicht normalisierten
Tabelle in die 1NF kann komplizierter als im Beispiel 6.1 sein. In der nächsten Tabelle ist
ein Buch in unnormalisierter Form dargestellt. Zu einem Buch kann es mehrere Autoren und
mehrere Stichworte haben.
ISBN
0-8053-0145-3
Die unnormalisierte Relation buch
Titel
Verlag
Autor
Princ.of DBS Benj./Cummings Elmasri
Navathe
Stichwort
RDBS
Lehrbuch
ooDBS
Die entsprechende Relation in 1NF ist in der nächsten Tabelle angegeben. Sie enthält 6 Tupel
und zwar ein Tupel pro Autor-/Stichwort- Kombination.
6-2
ISBN
0-8053-0145-3
0-8053-0145-3
0-8053-0145-3
0-8053-0145-3
0-8053-0145-3
0-8053-0145-3
Die Relation buch i 1NF
Titel
Autor
Princ.of DBS Benj./Cummings
Princ.of DBS Benj./Cummings
Princ.of DBS Benj./Cummings
Princ.of DBS Benj./Cummings
Princ.of DBS Benj./Cummings
Princ.of DBS Benj./Cummings
Verlag
Elmasri
Navathe
Elmasri
Navathe
Elmasri
Navathe
Stichwort
RDBS
RDBS
Lehrbuch
Lehrbuch
ooDBS
ooDBS
Wir können noch überlegen, wieso alle 6 Tupel notwendig sind um die volle Information zu
speichern. Wir betrachten dazu den folgenden Query.
πStichwort (σAutor=′ N avathe′ (buch))
Als Resultat erhalten wir die drei Stichworte “RDBS”, “Lehrbuch” und “ooDBS”. Lassen wir
aber in der Tabelle in 1NF zum Beispiel das letzte Tupel weg, so erhalten wir als Resultat nur
noch die beiden Stichworte “RDBS” und “Lehrbuch”. Die Information, dass zum gegebenen
Buch noch das Stichwort “ooDBS” gehört ist verlorengegangen.
6.2
Update-Anomalien
Die Lieferanten Relation im Beispiel 6.3 ist zwar jetzt in erster Normalform, enthält aber
Redundanz. Dies hat auf die Updateoperationen die folgenden negativen Konsequenzen.
INSERT: Ein Tuple kann erst eingetragen werden, wenn alle fünf Informationen vorhanden
sind, obwohl auch partielle Tupel wie z.B. (5, ’London’, 1000) bereits Aussenwelt Fakten
beinhalten können, die der Benutzer in der Datenbank speichern möchte. Auch Nullwerte
helfen hier nicht, da mindestens der Primärschlüssel (LNR, TBEZ) vorhanden sein muss.
DELETE: Liefert ein Lieferant nur ein Teil und wird diese Zeile gelöscht, so werden gleichzeitig die Informationen über den Lieferanten gelöscht. Bei einer späteren Wiederaufnahme von Lieferungen müssen die Daten des Lieferanten (Ort und Distanz) wieder
erfasst werden.
UPDATE: Falls eine Firma umzieht, so muss der Ort und die Distanz in allen Tupeln dieser
Firma nachgeführt werden.
Wollen wir diese Probleme lösen, so muss die Relation weiter normalisiert werden.
6.3
6.3.1
Funktionale Abhängigkeit
Definition
Bevor wir mit der Normalisierung weiterfahern müssen wir den Begriff funktionale Abhängigkeit
(kurz: FD vom englischen functional dependency) einführen, der für die Normalisierung von
Relationen eine fundamentale Rolle spielt. Eine FD ist eine lokale Integritätsbedingung.
6-3
Definition 6.2 [funktionale Abhängigkeit] Es sei R eine Attributmenge und
X, Y ⊆ R. Eine funktionale Abhängigkeit X → Y bezeichnet folgende semantische
Bedingung: sei r ∈ REL(R)
(X → Y )(r) :=
(
true falls (∀t1 , t2 ∈ r)(t1 (X) = t2 (X) ⇒ t1 (Y ) = t2 (Y ))
f alse sonst
Es folgt unmittelbar aus der Definition, dass eine Schlüsselbedingung eine spezielle FD ist:
Ist r ∈ REL(R)undK ⊆ R ein Schlüssel, so gilt:
(K → R)(r) = true =⇒ (∀t1 , t2 ∈ r)(t1 (K) = t2 (K) ⇒ t1 (R) = t1 = t2 (R)) = t2
Beispiel 6.4 [Funtionale Abhängigkeiten] Im Beispiel 6.3 mit
R = {(L)N R, (O)RT, (D)IST, (T )BEZ, (A)N Z}
gelten die folgenden funktionalen Abhängigkeiten
LT → A
O→D
L→O
Dabei ist {LNR, TBEZ} Schlüssel für die Relation.
Bemerkung 6.2 [Semantischer Begriff] Der Begriff der funktionalen Abhängigkeit ist ein
semantischer Begriff. Das Erkennen von funktionalen Abhängigkeiten ist nur möglich, wenn
die Bedeutung der Daten bekannt ist. Die Tatsache, dass der Ort von der Lieferantennummer
abhängig ist, modeliert die Tatsache, dass jeder Lieferant an genau einem Ort ansässig ist.
Für funktionale Abhängigkeiten vereinbaren wir die folgenden Schreibweisen:
• Einzelne FDs der Form X → Y versehen wir häufig mit einem Namen und schreiben
dann: f : X → Y
• Mit Lf bzw. Rf bezeichnen wir die linke bzw. die rechte Seite einer FD f.
• Mit attr(f ) bezeichnen wir alle Attribute einer FD das heisst, attr(f ) = Lf ∪ Rf .
6.3.2
Implikation von Funktionalen Abhängigkeiten
Eine Menge von FDs kann als eine Menge von lokalen Integritätsbedingung für eine Relation
angesehen werden. Insbesondere kann für ein Relationenschema R und eine Menge F von FDs
mit (∀f ∈ F )attr(f ) ⊆ R, R = (R, F ) als erweitertes Relationenschema betrachtet werden.
Wir interessieren uns nun für die Menge aller gültigen Relationen SATR (F ).
Definition 6.3 [Äquivalenz] Seien F und G zwei FD-Mengen über dieselbe
Attributmenge R.
(i) F und G heissen äquivalent, kurz F ≈ G, falls SATR (F ) = SATR (G) gilt.
(ii) F heisst redundant, falls eine FD-Menge F ′ ⊂ F existiert mit F ′ ≈ F .
6-4
In Worten ausgedrückt heisst das, dass zwei FD-Mengen F und G dann äquivalent sind, falls
sie als semantische Spezifikationen gleich sind. Redundant bedeutet dann, dass eine gegebene
Spezifikation F mehr Bedingungen als nötig enthält.
Wir können die Äquivalenz bzw. Redundanz auf einen Grundsätzlicheren Begriff reduzieren:
Definition 6.4 [Implikation] Sei F eine FD-Menge über die Attributmenge R
und f eine FD mit attr(f ) ⊆ R. F impliziert f , kurz F |= f , falls SATR (F ) ⊆
SATR (f ) gilt. D.h. jede Relation über R, welche F erfüllt, erfüllt auch f .
Damit ergibt sich sofort:
Satz 6.1 [Äquivalenz] Seien F und G zwei FD-Mengen über dieselbe AttributMenge R.
(i) F ≈ G ⇐⇒ ((∀f ∈ F ) G |= f ) ∧ ((∀g ∈ G) F |= g)
(ii) F ist redundant ⇐⇒ (∃f ∈ F ) F \ {f } |= f
Beweis:
(i) =⇒:
Sei f ∈ F dann ist (∀r ∈ SATR (F ))r ∈ SATR (f ) also SATR (F ) ⊆ SATR (f )
nach Voraussetzung SATR (F ) = SATR (G) folgt SATR (G) ⊆ SATR (f ) und
damit G |= f . (dito für g ∈ G)
⇐=:
Sei r ∈ SATR (F ) nach Voraussetzung gilt, dass (∀g ∈ G)r ∈ SATR (g) und
damit r ∈ SATR (G) (dito für r ∈ SATR (G)).
(ii) trivial
Im folgenden werden wir uns mit der Frage beschäftigen, wie Implikationen getestet werden
können. Aus einer Antwort auf dieser Frage sind dann Äquivalenz- und Redundanz-Tests mit
Satz 6.1 herleitbar.
Beispiel 6.5 [Äquivalenz] Wir betrachten das Beispiel 6.3 mit der Relation R = {(L)N R, (O)RT,
(D)IST, (T )BEZ, (A)N Z} und den beiden folgenden FD-Mengen:
F = {LT → A, O → D, L → O, L → D}
und
G = {LT → A, O → D, L → O}
Man rechnet schnell nach, dass die Relation im Beispiel 6.3 sowohl F wie auch G erfüllt.
Allgemeiner kann man durch Anwendung von Definition 6.2 ebenso zeigen, dass jede Relation
aus REL(R) die F erfüllt auch G erfüllt und umgekehrt. Sei s eine Relation, die G erfüllt,
und sei f : {LN R} → {DIST }, so gilt:
(∀t1 , t2 ∈ s) t1 (LN R) = t2 (LN R)
=⇒ t1 (ORT ) = t2 (ORT ), da s L → O erfüllt
=⇒ t1 (DIST ) = t2 (DIST ), da s O → D erfüllt
Also ist auch L → D in s erfüllt und es gilt F ≈ G. Ferner ist F redundant, da G ⊂ F gilt.
6-5
6.3.3
Ableitung von funktionalen Abhängigkeiten
Wir wollen den Implikationsbegriff näher untersuchen und algorithmische Hilfsmittel angeben,
mit denen man Implikationen testen kann. Wir führen zunächst einen weiteren Begriff ein:
Definition 6.5 [Hülle] Sei F eine FD-Mengen über die AttributMenge R, dann
heisst
[
F + := {f |attr(f ) ⊆
attr(g) ∧ F |= f }
g∈F
die Hülle von F
Aus der Definition folgt sofort, dass F ⊆ F + für jede FD-Menge F . Testen einer Implikation
bedeutet also festzustellen, ob eine gegebene FD f Element der Hülle einer FD-Menge F ist.
Das Problem zu entscheiden, ob zu gegebenem F und f f ∈ F + gilt, heisst das MembershipProblem funktionaler Abhängigkeiten.
Wir wollen nun Regeln angeben aufstellen, die es erlauben gewisse Elemente einer Hülle zu
bestimmen.
Satz 6.2 [Amstrong-Axiome] Sei R eine Attributmenge und X, Y, Z, W ⊆ R
dann gilt:
(A1) Y ⊆ X =⇒ X → Y (Triviale Abhängigkeit)
(A2) X → Y ∧ Y → Z =⇒ X → Z (Transitivität)
(A3) X → Y ∧ Z ⊆ W =⇒ XW → Y Z (Erweiterung)
Die Regeln (A1) bis (A3) heissen Amstrong Axiome
Beweis:
(A1) Y ⊆ X → (∀t1 , t2 ∈ R)t1 (X) = t2 (X) → t1 (Y ) = t2 (Y )
Diese Aussage ist trivial.
(A2) siehe Beispiel 6.5
(A3) Sei r ∈ SATR (X → Y ) zu zeigen ist, dass r auch die FD XW → Y Z
erfüllt. Es seien t1 , t2 ∈ r mit t1 (XW ) = t2 (XW ) aus X → Y folgt, dass
t1 (Y ) = t2 (Y ). Wegen Z ⊆ W und t1 (W ) = t2 (W ) folgt, dass t1 (Z) = t2 (Z).
Zusammengesetzt ergibt sich t1 (Y Z) = t2 (Y Z).
Aus Satz 6.2 können wir die Herleitung von neuen FDs aus gegebnen folgendermassen formalisieren:
Definition 6.6 [Ableitung] Es sei F eine Menge von FDs über R und f : X → Y
eine FD mit X, Y ⊆ R. f heisst aus F ableitbar F ⊢ f , falls eine Folge f1 , . . . , fn
von FDs existiert mit:
(1) fn hat die Form X → Y
(2) Für 1 ≤ i ≤ n gilt entweder fi ∈ F oder, dass fi aus {f1 , . . . , fi−1 } mit Hilfe
der Regeln (A1),(A2),(A3) erzeugbar ist.
6-6
Das Regelsystem bestehend aus (A1), (A2) und (A3) ist korrekt, da jede damit realisierte
Ableitung auf implizierte (gültige) FDs führt. Der folgende Satz sagt nun aus, dass das System
auch vollständig ist. D.h., dass jede aus F implizierte FD auch aus F ableitbar ist.
Satz 6.3 [Korrektheit und Vollständigkeit] Sei F eine Menge von FDs und
f eine FD. Dann gilt:
F |= f ⇐⇒ F ⊢ f
Beweis: Siehe [Vos00]
Bemerkung 6.3 [Weitere Regeln] Aus den Regeln (A1),(A2) und (A3) lassen sich noch
die folgenden Regeln ableiten:
(A4) X → Y ∧ X → Z =⇒ X → Y Z (Additivität)
(A5) X → Y ∧ Z ⊆ Y =⇒ X → Z (Projektivität)
(A6) X → X (Reflexivität)
(A7) X → Y Z ∧ Z → AW =⇒ X → Y ZA (Akkumulation)
Das Regelsystem (A5),(A6),(A7) ist auch vollständig und korrekt
Um das Membership-Problem für eine FD f zu lösen können wir also im Prinzip die Hülle
der FD-Menge F berechnen und testen ob f in der Hülle vorhanden ist. Im allgemeinen Fall
ist jedoch |F + | exponentiell in F wie das folgende Beispiel zeigt.
Beispiel 6.6 [Kardinalität der Hülle] Sei R = {A, B1 , . . . , Bn } und F = {A → B1 , . . . , A →
Bn } in diesem Fall gilt:
(∀Y ⊆ R) A → Y
Das heisst, |F + | ≥ 2n .
Um das Membership-Problem in linearer Zeit lösen zu können führen wir einen weiteren
Hüllenoperator ein.
Definition 6.7 [CL-Operator] Für X ⊆ R und eine FD-Menge F über R heisst
clF (X) := {A ∈ R|X → A ∈ F + }
die Hülle von X unter F .
Satz 6.4 [Membership-Problem] Sei F eine FD-Menge über R und X, Y ⊆ R
dann gilt:
X → Y ∈ F + ⇐⇒ Y ⊆ clF (X)
Beweis:
=⇒:
Es gelte X → Y ∈ F + . Dann folgt X → A ∈ F + für jedes A ∈ Y nach Regel (A5)
und daher Y ⊆ clF (X).
⇐=:
Es gelte Y ⊆ clF (X). Dann gilt clF (X) → Y ∈ F + nach Regel (A1). Da nach
Definition X → clF (X) ∈ F + gilt, so folgt mit Regel (A2) X → Y ∈ F + .
6-7
Wir sind nun in der Lage einen Algorithmus anzugeben, der clF (X) in einer Zeit berechnet,
die polynomiell in der Länge der Darstellung von F ist.
Algorithmus 6.1 [Hülle]
Input: Eine FD-Menge F und eine Attributmenge X
Output: clF (X)
Methode:
begin
Y:=X
while (∃S → T ∈ F ) S ⊆ Y ∧ T 6⊆ Y
Y := Y ∪ T
endwhile
end
Mit Hilfe des Algorithmus 6.1 kann ein Algorithmus für das Membership-Problem entwickelt
werden.
Algorithmus 6.2 [FD-MEMBERSHIP]
Input: F (
, X, Z
true
falls X → Z ∈ F +
Output:
f alse sonst
Methode:
begin
membership := false;
Y := Hülle(F, X);
if Z ⊆ Y then
membership := true
endif
end;
Man kann zeigen, dass der Algorithmus in linearer Zeit bezüglich der Länge der Darstellung
des inputs von Hülle ist.
6.3.4
Anwendung des Membership-Algorithmus
Wir wollen noch zwei Anwendungen des FD-MEMBERSHIP Algorithmus anschauen.
Als erstes möchten wir für das Relationenschema R, für das eine Menge F von FDs als
Abhängigkeiten gefordert ist, einen Schlüssel berechnen.
Algorithmus 6.3 [KEY] Input: Eine Attributmenge R = {A1 , . . . , An } und eine
FD-Menge F
Output: Ein Schlüssel K ⊆ R für die Relation R
Methode:
6-8
begin
K := R;
for i:=1 to n do
if FD-MEMBERSHIP(F ,K \ {Ai },R) then
K := K \ {Ai }
endif
endfor
end
Als Ergebnis erhält man eine Menge K mit K → R ∈ F + und K ′ → R 6∈ F + für alle K ′ ⊂ K.
Beispiel 6.7 [Schlüssel] Wir betrachten das Lieferantenbeispiel 6.3. Dort haben wir:
R = {(L)N R, (O)RT, (D)IST, (T )BEZ, (A)N Z} und die FD-Menge
F = {LT → A, O → D, L → O}
1.Schritt:
clF (ODT A) = ODT A =⇒ ODT A ist kein Superschlüssel.
2.Schritt:
clF (LDT A}) = LODT A =⇒ LDT A ist ein Superschlüssel.
3.Schritt:
clF (LT A) = LODT A =⇒ LT A ist ein Superschlüssel.
4.Schritt:
clF (LA) = LODA =⇒ LA ist kein Superschlüssel.
5.Schritt:
clF (LT ) = LODT A =⇒ LT ist ein Superschlüssel.
=⇒ LT ist ein Schlüssel
Es ist zwar im Sinne der Komplexität einfach irgend einen Schlüssel in einer Relation zu
finden. Hat es aber mehrere Schlüssel in der Relation und möchte man den Schlüssel K mit
|K| ist minimal, so ist das Problem sofort schwierig, wie der folgende Satz zeigt.
Satz 6.5 [Schlüssel mit minimaler Kardinalität] Sei F eine FD-Menge über
R und k eine positive ganze Zahl. Dann ist das Problem zu entscheiden, ob es
einen Schlüssel K ⊆ R gibt mit |K| ≤ k, NP-vollständig.
Beweis: siehe [Vos00].
Wir wollen eine zweite Anwendung des FD-MEMBERSHIP Algorithmus betrachten und zwar
das Problem eine Basis für eine FD-Menge zu finden. Dafür definieren wir als erstes, was eine
Basis ist.
Definition 6.8 [Überdeckung] Es seien F und G FD-Mengen mit F ≈ G. Dann
heisst F Überdeckung (engl. Cover) von G (und entsprechend G Überdeckung von
F ).
Definition 6.9 [Basis] Es sei F eine FD-Menge und f : X → Y ∈ F :
6-9
(i) f heisst l-minimal (l für links) bzw. voll, falls kein X ′ ⊂ X existiert mit
F \ {f } ∪ {X ′ → Y } ≈ F
(ii) f heisst r-minimal (r für rechts) falls |Y | = 1 ist.
(iii) F heisst minimal, falls jedes Element von K l- und r-minimal ist.
(iv) Eine Überdeckung G von F heisst Basis von F, falls G minimal und nonredundant ist.
Der Begriff Basis kann hier in Analogie zum Begriff der Basis in einem Vektorraum verstanden
werden: jedes Element der Hülle von F ist aus den Elementen der Basiselemente ableitbar. In
einem Vektorraum ist jeder Vektor als Linearkombination der Basis darstellbar.
Im Hinblick auf Updateoperationen führt die Angabe einer Basis dazu, dass man möglichst
wenig Abhängigkeiten überprüfen muss.
Der folgende Algorithmus berechnet eine Basis für eine FD-Menge in polynomieller Zeit.
Algorithmus 6.4 [BASIS] Input: F = {f1 , . . . , fn }
Output: Eine Basis G von F
Methode:
begin
/* r-minimal */
G := ∅
for each f ∈ F do
for each A ∈ Rf do
G := G ∪ {X → A}
endfor
endfor
/* l-minimal */
for each f ∈ G do
for each B ∈ Lf do
if FD-MEMBERSHIP(G, Lf \ {B}, Rf ) then
Lf := Lf \ {B}
endif
endfor
endfor
/* entfernen von redundanten FDs */
for each f ∈ G do
if FD-MEMBERSHIP(G \ {f }, Lf , Rf ) then
G := G \ {f }
endif
endfor
end
Beispiel 6.8 [Basis] Als Beispiel betrachten wir wieder das Schema
R = {(L)N R, (O)RT, (D)IST, (T )BEZ, (A)N Z}
6-10
und die FD-Menge
F = {LT → OA, O → D, L → OD}
1.Schritt r-minimal
G = {LT → O, LT → A, O → D, L → O, L → D}
2.Schritt l-minimal
Weil clF (L) = LOD und clF (T ) = T gilt nach diesem Schritt:
G = {LT → A, O → D, L → O, L → D}
3.Schritt entfernen von Redundanzen Da cl{LT →A,O→D,L→O} (L) = LOD kann die FD L → D
aus G entfernt werden.
G = {LT → A, O → D, L → O}
6.4
Zweite-, dritte- und Boyce-Codd-Normalform
In diesem Abschnitt betrachten wir ausschliesslich Relationenschemata mit FDs als lokalen
Integritätsbedingungen. Ferner sollen alle Schemata in 1NF sein.
Wir betrachten nun unser Beispiel R = (R, F ) mit
R = {(L)N R, (O)RT, (D)IST, (T )BEZ, (A)N Z}
und
F = {LT → A, O → D, L → O}
genauer und beobachten zuerst, dass LT der einzige Schlüssel ist (siehe Beispiel 6.7).
Definition 6.10 [Schlüsselattribut] Sei R = (R, F ). A heisst Schlüsselattribut
(bzw. A ist prim) falls es einen Schlüssel K für R gibt mit A ∈ K. (Andernfalls
heisst A Nichtschlüssel-Attribut oder kurz NSA).
In unserem Beispiel sind also LN R und T BEZ prim, AN Z, ORT und DIST sind hingegen
NSAs. DA LT Schlüssel ist, so sind LT → A, LT → O und LT → D aus F ableitbar und
r-minimal. Andererseits ist LT → O nicht l-minimal auf Grund der existenz von L → O in F .
Ferner können wir beobachten, dass ein NSA (DIST ) existiert, dass von einem anderen NSA
(ORT ) abhängig ist. Diese Feststellung resultiert aus den beiden FDs L → O und O → D
aus denen die “transitive” FD L → D herleitbar ist.
Definition 6.11 [Transitive Abhängigkeit] Sei R = (R, F ). A ∈ R heisst
transitiv abhängig von X ⊆ R falls gilt:
(i) X → A ∈ F + ∧ A 6∈ X
(ii) (∃Y ⊆ R)[X → Y ∈ F + ∧ Y → X 6∈ F + ∧ Y → A ∈ F + ∧ A 6∈ Y ]
Ist A nicht transitiv von X abhängig und ist X → A ∈ F + , so heisst A direkt
abhängig von X (bzw. X → A direkte FD).
6-11
Bedingung (i) besagt dass die FD X → A nicht trivial ist. Bedingung (ii) schliesst aus, dass
X und Y äquivalent sind und dass Y → A trivial ist.
Wir sind nun in der Lage die Normalformen zu definieren, die die semantischen Probleme im
Zusammenhang mit FDs ausschliessen.
Definition 6.12 [Normalformen] Es sei R = (R, F ) ein Relationenschema in
1NF.
(i) R ist in zweiter Normalform (2NF), falls für jedes NSA A ∈ R und jedem
Schlüssel K für R gilt, dass die FD K → A l-minimal ist.
(ii) R ist in dritter Normalform (3NF), falls für jedes NSA A ∈ R und jeden
Schlüssel K für R gilt, dass die FD K → A direkt ist.
(iii) R ist in Boyce-Codd-Normalform (BCNF), falls jedes Attribut A ∈ R von
jedem Schlüssel K für R direkt abhängig ist.
In unserem Beispiel gilt also folgendes: R ist nicht in 2NF, da LT → O nicht voll ist. R ist
nicht in 3NF, da D transitiv abhängig von L ist; es gilt:
L → D ∈ F + ∧ D 6∈ L ∧ [L → O ∈ F + ∧ O → L 6∈ F + ∧ O → D ∈ F + ∧ D 6∈ O]
Der folgende Satz stellt einen Zusammenhang zwischen den verschiedenen Normalformen her.
Satz 6.6 [Zusammenhang zwischen Normalformen] Sei R = (R, F ) in 1NF.
Dann gilt:
(i) R ist in BCN F =⇒ R ist in 3NF
(ii) R ist in 3NF =⇒ R ist in 2NF
Beweis: (i) ist trivial, da die BCNF-Bedingung insbesondere alle Nichtschlüsselattribute erfasst.
(ii) Wir zeigen hier die Kontraposition: R sei nicht in 2NF dh. es existiert ein NSA
A ∈ R und ein Schlüssel K so, dass K → A nicht voll ist. Daraus folgt:
(∃Z ⊂ K) Z → A ∈ F +
Weiter gilt Z → K 6∈ F + aufgrund der Minimalität von K und A 6∈ K, da A nicht
prim ist. Insgesamt gilt also
K → A ∈ F + ∧ A 6∈ K ∧ [K → Z ∈ F + ∧ Z → K 6∈ F + ∧ Z → A ∈ F + ∧ A 6∈ Z]
Das heisst, A ist sogar transitiv abhängig von K. Also ist R nicht in 3NF.
Die zwei folgenden Beispiele zeigen, dass die Umkehrung der Aussagen von Satz 6.6 im allgemeinen falsch sind.
Beispiel 6.9 [2NF aber nicht 3NF] Wir nehmen das folgende Beispiel:
R = {(L)N R, (O)RT, (D)IST }
F = {L → O, O → D}
6-12
In diesem Beispiel ist LN R offensichtlich der einzige Schlüssel. L → O und L → D sind beide
l-minimal. Also ist (R, F ) in 2NF. Ferner gilt:
L → D ∈ F + ∧ D 6∈ L ∧ [L → O ∈ F + ∧ O → L 6∈ F + ∧ O → D ∈ F + ∧ D 6∈ O]
Also ist DIST von LN R transitiv abhängig und somit ist (R, F ) nicht in 3NF.
Beispiel 6.10 [3NF aber nicht BCNF] Wir betrachten nun das folgende Beispiel:
R = {(S)tudent, (F )ach, (D)ozent}
F = {SF → D, D → F }
Die beiden FD bedeuten, dass ein Student in einem Fach von genau einem Dozenten unterrichtet wird und dass ein Dozent nur ein Fach unterrichtet.
SF und SD sind die einzigen Schlüssel für (R, F ). In diesem Fall sind alle Attribute prim, so
dass die Forderung der 3NF trivialerweise erfüllt ist. Andererseits gilt, dass SD → F transitiv
ist wegen
SD → F ∈ F + ∧ F 6∈ SD ∧ [SD → D ∈ F + ∧ D → SD 6∈ F + ∧ D → F ∈ F + ∧ F 6∈ D]
Das heisst, (R, F ) ist nicht in BCNF.
Die 3NF und BCNF kann man auch folgendermassen charakterisieren.
Satz 6.7 [3NF und BCNF] Sei R = (R, F ). Dann gilt:
(i) R ist in 3NF ⇐⇒
(∀X ⊆ R)(∀A ∈ R ∧ A N SA)[X → A ∈ F + ∧ A 6∈ X =⇒ X → R ∈ F + ]
(ii) R ist in BCNF ⇐⇒
(∀X ⊆ R)(∀A ∈ R)[X → A ∈ F + ∧ A 6∈ X =⇒ X → R ∈ F + ]
Beweis: siehe [Vos00]
Bemerkung 6.4 [BCNF und Superschlüssel] Satz 6.7 (i) bedeutet, dass ein Relationenschema genau dann in 3NF ist, wenn für jede nichttriviale FD über R gilt, dass entweder die
linke Seite ein Superschlüssel ist oder dass die rechte Seite prim ist.
Satz 6.7 (ii) bedeutet, dass ein Relationenschema genau dann in BCNF ist, wenn die linke
Seite jeder nichttrivialen FD über R ein Superschlüssel ist.
Als nächstes fragen wir uns, ob es möglich ist, effiziente Tests anzugeben, mit denen man
feststellen kann, ob ein gegebenes Relationenschema in 3NF bzw. in BCNF ist.
Um 3NF zu prüfen, können wir auf Grund von Satz 6.7 folgendes Vorgehen angeben.
Algorithmus 6.5 [Test 3NF]
input: R(= (R, F )
true
falls R in 3NF
output:
f alse sonst
Methode:
6-13
begin
G := BASIS(F);
for each g ∈ G do
if FD-MEMBERSHIP(F, Lg , R) = false then
if Rg ist NSA then
return false
endif
endif
endfor
return true
end
Nun zur Komplexität: Das berechnen von BASIS und FD-MEMBERSHIP ist polynomial.
Leider kann nicht effizient berechnet werden ob ein Attribut prim ist oder nicht.
Satz 6.8 [Komplexität prim] Sei R = (R, F ). Dann ist das Problem zu entscheiden, ob ein A ∈ R prim ist, NP-vollständig.
Aus dem Satz 6.8 kann durch polynomielle Reduktion gezeigt werden:
Satz 6.9 [Komplexität 3NF] Sei R = (R, F ). Dann ist das Problem zu entscheiden, ob R in 3NF ist, NP-vollständig.
Um BCNF zu prüfen, können wir auf Grund von Satz 6.7 folgendes Vorgehen angeben.
Algorithmus 6.6 [Test BCNF]
input: R(= (R, F )
true
falls R in BCNF
output:
f alse sonst
Methode:
begin
G := BASIS(F);
for each g ∈ G do
if FD-MEMBERSHIP(F, Lg , R) = false then
return false
endif
endfor
return true
end
In diesem Algorithmus fällt der Test auf NSA weg, so dass der Algorithmus polynomial ist.
Satz 6.10 [Komplexität BCNF] Sei R = (R, F ). Dann ist das Problem zu
entscheiden, ob R in BCNF ist, in polynomialer Zeit lösbar.
6-14
6.5
Dekomposition und Syntheseverfahren
Wir kommen nun auf das Beispiel 6.3 zurück
R = ({(L)N R, (O)RT, (D)IST, (T )BEZ, (A)N Z}, {LT → A, L → O, O → D})
Dieses Beispiel ist mit Anomalien und Redundanzen behaftet. Wenn wir das Schema analysieren
kommen wir zum Schluss, dass es nicht in 2NF ist und daher weder in 3NF noch in BCNF
ist. In diesem Abschnitt wollen wir zeigen, dass durch geeignete Zerlegung von R ein neues
Datenbankschema erzeugt werden kann, dass einerseits dieselbe Anwendung modeliert wir R,
andererseits aber die Mängel von R nicht mehr hat.
Betrachten wir etwa das Datenbankschema D = {R1 , R2 , R3 } mit
R1 = ({(L)N R, (T )BEZ, (A)N Z}, {LT → A})
R2 = ({(L)N R, (O)RT }, {L → O})
R3 = ({(O)RT, (D)IST }, {O → D})
So fällt auf, dass R und D eng verwandt sind. Sie enthalten dieselben Attributte sowie dieselben Abhängigkeiten. Ferner sind alle drei Schemata in BCNF. Die nächste Abbildung zeigt
die Projection der Tabelle aus dem Beispiel 6.3 auf die drei neuen Schemata.
(L)NR
1
1
1
1
2
2
3
4
4
4
4
r1 (R1 )
(T)BEZ
T1
T2
T3
T4
T1
T2
T2
T2
T2
T4
T5
(A)NZ
300
200
400
200
450
300
200
150
150
750
350
r2 (R2 )
(L)NR (O)RT
1
London
2
Paris
3
Paris
4
Berlin
r3 (R3 )
(O)RT (D)IST
London 1000
Paris
570
Berlin
960
Die Relationen r1 , r2 und r3 sind jetzt frei von den früher festgestellten Anomalien und
Redundanzen. Ferner gilt auch:
r(R) = r1 (R1 ) ⊲⊳ r2 (R2 ) ⊲⊳ r3 (R3 )
Wir wollen diesen Ansatz des Entwurfs durch Normalisierung, der allgemein ein nicht normalisiertes Datenbankschema durch ein normalisiertes zu ersetzen versucht genauer untersuchen.
Wir beschränken uns auf den Fall, dass das unnormalisierte Schema ein einzelnes Universalschema der Form R = (U, F ) ist. Falls wir nun R durch D ersetzen wollen, so müssen wir
Kriterien haben, die uns sagen, wann R und D dieselbe Anwendung beschreiben. Als erstes
definieren wir:
Definition 6.13 [Normalisiertes Datenbankschema] Ein Datenbankschema
D = (R, .) ist in 2NF (bzw. 3NF, BCNF), falls jedes Ri ∈ R in 2NF (bzw. 3NF,
BCNF) ist.
6-15
Ein erstes Kriterium für die Zerlegung ist die Forderung, dass keine Attributte verlorengehen.
Das heisst, für R = (U, .) und D = (R, .) mit R = {R1 , . . . , Rk } gilt
∪ki=1 Ri = U
.
Die Zerlegung muss ferner unabhängig sein. Dies bedeutet, dass ursprünglichen FDs erhalten
bleiben. Diese Forderung des erhalten der semantischen Spezifikation aus R = (U, F ) durch
D = (R, .) mit Ri = (Ri , Fi ) lautet formal:
(∪ki=1 Fi )+ = F +
und wird durch das nächste Beispiel motiviert:
Beispiel 6.11 [Verlust von FDs] Wir betrachten wieder das Schema R = (R, F ) aus dem
Beispiel 6.10, das nicht in BCNF ist.
R = {(S)tudent, (F )ach, (D)ozent}
F = {SF → D, D → F }
Die beiden FD bedeuten, dass ein Student in einem Fach von genau einem Dozenten unterrichtet wird und dass ein Dozent nur ein Fach unterrichtet.
Wir ersetzen nun R durch das Datenbankschema D = ({R1 , R2 }, ∅) mit
R1 = ({(S)tudent, (D)ozent}, ∅),
R2 = ({(D)ozent, (F )ach}, {D → F })
Bei dieser Zerlegung bleibt die FD D → F erhalten. Die FD SF → D geht hingegen verloren.
Wenn wir zum Beispiel in der Tabelle Student_BCNF das neue Tupel (’Paul’, ’Wyss’) eintragen,
so wird Paul nun im gleichen Fach von zwei verschiedenen Dozenten unterrichtet.
Student_BCNF
Student Dozent
Hans
Meier
Hans
Wyss
Paul
Meier
Paul
Bracher
Dozent_BCNF
Dozent Fach
Meier
Mathematik
Wyss
Physik
Bracher Physik
Das Beispiel zeigt, dass nicht jede Relation unabhängig in ein Schema in BCNF zerlegt werden
kann.
Als letzte Forderung verlangen wir, dass die Zerlegung frei von Informationsverlusten ist. Dies
bedeutet anschaulich, dass die Ursprüngliche Universalrelation immer exakt aus dem aktuellen
Datenbankzustand rekonstruiert werden kann.
Beispiel 6.12 [Zerlegung mit Informationsverlust] Gegeben sei das Relationenschema
R = (R, F ) mit:
R = {(K)U N DE, KON T O − (N )U M M ER, (S)ALDO, (Z)W EIGST ELLE}
F = {K → S, N → ZS}
Wir betrachten nun die folgende Zerlegung von R
6-16
(K)UNDE
Peter
Paul
r(R)
KONTO-(N)UMMER (S)ALDO
1-5000-72
10000
7-2355-80
10000
KONTO-(N)UMMER
1-5000-72
7-2355-80
r1 (R1 )
(S)ALDO
10000
10000
(Z)WEIGSTELLE
Bern
Zürich
(Z)WEIGSTELLE
Bern
Zürich
r2 (R2 )
(K)UNDE (S)ALDO
Peter
10000
Paul
10000
Für die beiden Systeme liefert nun die Anfrage “zeige alle Zweigstellen, bei denen Peter ein
Konto hat” die folgenden Resultate.
= {Bern}
Ursprüngliche Relation: πZ (σK=′ P eter′ (R))
Zerlegte Relationen:
πZ (σK=′ P eter′ (R1 ⊲⊳ R2 )) = {Bern, Zürich}
Der Verbund von r1 und r2 ist in diesem Fall also verlustbehaftet. Obschon wir mehr Tupel
als Antwort erhalten, erhalten wir weniger Informationen.
6.5.1
Dekomposition
Auf Grund der bisherigen Informationen legen wir jetzt fest:
Definition 6.14 [Einschränkung von FDs] Für eine Attributmenge X und
eine FD-Menge F sei die Einschränkung von F auf X definiert durch
πX (F ) := {f ∈ F |attr(f ) ⊆ X}
Definition 6.15 [Zerlegung] Es sei R = (U, F ) und D = (R, .) mit Ri = (Ri , Fi )
für jedes Ri ∈ R, 1 ≤ i ≤ k.
(i) D heisst Zerlegung von R, falls gilt:
(a) ∪ki=1 Ri = U
(b) (∀i, 1 ≤ i ≤ k)Fi+ ⊆ πRi (F + )
(ii) Eine Zerlegung D von R heisst
(a) verlustlos (bzgl. R) falls gilt: (∀r ∈ SAT (R)) r = ⊲⊳ki=1 πRi (r)
(b) unabhängig (bzgl. R) falls gilt: (∪ki=1 Fi )+ = F +
der folgende Satz (ohne Beweis) motiviert das sogenannte Dekompositionsverfahren).
Satz 6.11 [Binäre Dekomposition] Es sei R = (R, U ), U = XY Z. Falls
X → Y ∈ F + gilt, so ist die Zerlegung D = ({R1 , R2 }, ∅) mit R1 = (XY, πXY (F ))
und R2 = (XZ, πXZ (F )) verlustlos.
Wir können nun das Dekompositionsverfahren angeben, das zu einem Universalschema R =
(U, F ) eine verlustlose Zerlegung erzeugt, die in BCNF ist.
6-17
Algorithmus 6.7 [DEKOMPOSITION]
Input: Ein Universalschema R = (U, F )
Output: Eine verlustlose BCNF-Zerlegung D = (R, .) von R
Methode:
begin
R := {R}
done := false
while not done do
if (∃Ri ∈ R) Ri nicht in BCNF
d.h. (∃Y → Z ∈ Fi+ ∧ Z 6⊆ Y ) Y → Ri 6∈ Fi+ then
Ri1 := Y Z
Ri2 := Ri \ Z
Ri1 = (Ri1 , πRi1 (Fi+ ))
Ri2 = (Ri2 , πRi2 (Fi+ ))
R = (R \ {Ri }) ∪ {Ri1 , Ri2 }
else
done := true
endif
endwhile
end
Wir beweisen die Korrektheit des Algorithmus nicht formal. Wir bemerken aber, dass die
BCNF der Komponenten erzwungen wird. Ferner sind alle Dekompositionen nach Satz 6.11
verlustlos.
Das folgende Beispiel zeigt, dass unter Umständen Abhängigkeiten während der Zerlegung
verloren gehen, so dass das Resultat im allgemeinen nicht unabhängig ist.
Beispiel 6.13 [Dekomposition] Wir verwenden wieder unser Beispiel
R = ({(L)N R, (O)RT, (D)IST, (T )BEZ, (A)N Z}, {LT → A, L → O, O → D})
Wir wissen schon, dass R nicht in BCNF ist.
1.Schritt:
L → O ∈ F + ∧ L → LODT A 6∈ F + also Zerlegen wir in
R1 = ({LO}, {L → O}) und
R2 = ({LDT A}, {LT → A, L → D})
2.Schritt:
R1 ist in BCNF, R2 jedoch nicht wegen L → LDT A 6∈ F2+ . Daher wird R2 ersetzt durch
R21 = (LD, {L → D}) und
R22 = (LT A, {LT → A})
Nun ist D = ({R1 , R21 , R22 }, .) in BCNF, jedoch gilt O → D 6∈ (F1 ∪ F21 ∪ F22 )+ .
Man rechnet leicht nach, dass die folgende Zerlegung auch als Dekompositionsergebnis möglich
ist:
R1 = (LT A, {LT → A})
R2 = (LO, {L → O})
R3 = (OD, {O → D})
6-18
Diese Zerlegung ist sogar unabhängig.
Aus dem Beispiel 6.13 geht hervor, dass die Dekomposition keine eindeutige Zerlegung liefert.
Dies kommt daher, dass in der if-Anweisung die nächste FD nichtdeterministisch ausgewählt
werden kann.
Bemerkung 6.5 [BCNF und Unabhängigkeit] Es sei bemerkt, dass es Fälle gibt, in
denen BCNF und Unabhängigkeit nicht gleichzeitig erreichbar sind. Das Beispiel 6.11 liefert
gerade einen solchen Fall.
Man kann ferner zeigen, dass das Problem zu entscheiden, ob zu gegebenem R = (U, F ) eine
verlustlose und unabhängige BCNF-Zerlegung existiert, NP-hart ist.
Ein weiteres Problem der Dekomposition besteht darin, dass stets F + (bzw. Fi+ ) berechnet werden muss. Diese Berechnung ist aber wie wir schon wissen im allgemeinen nur mit
exponentiellem Aufwand durchführbar.
6.5.2
Synthese
Aufgrund der Probleme im Zusammenhang mit der BCNF wollen wir die Forderung nach einer
BCNF-Zerlegung abschwächen in die Forderung einer 3NF-Zerlegung. Das folgende Verfahren
erreicht eine verlustlose und unabhängige 3NF-Zerlegung und ist unter dem Namen Synthese
bekannt.
Die Idee ist, dass die linke Seite einer FD eine Entität oder Beziehung darstellt und die
rechte Seite die dazugehörigen Eigenschaften. Dies wird benutzt um eine Konzepttrennung im
Universum vorzunehmen. Diese Art der Trennung bezeichnet man auch als das “one fact in
one place”-Prinzip des Datenbankentwurfs.
Algorithmus 6.8 [Synthese]
Input: R = (U, F )
Output: Eine verlustlose, unabhängige 3NF-Zerlegung D von R
Methode:
begin
G := BASIS(F )
R := ∅; i := 0
for each Y → A ∈ G do
i := i + 1
Ri := Y ∪ {A ∈ U |Y → A ∈ G}
Ri := (Ri , πRi (G))
R := R ∪ {Ri }
endfor
if (∀Ri ∈ R) Ri → U 6∈ G+ then
i := i + 1
Ri := KEY(U, G)
Ri := (Ri , ∅)
R := R ∪ {Ri }
endif
D = (R, ∅)
end
6-19
Einen Beweis der Korrektheit des Algorithmus kann in [Vos00] gefunden werden.
Beispiel 6.14 [Synthese] Wir betrachten wieder das Bankenbeispiel mit der folgenden Definition:
R = {(K)U N DE, KON T O − (N )U M M ER, (S)ALDO, (Z)W EIGST ELLE}
F = {K → S, N → ZS}
Die erste foreach Schlaufe liefert die folgenden Relationen:
R1 = (KS, {K → S})
R2 = (N ZS, {N → Z, N → S})
Da KS → R 6∈ F + und N ZS → R 6∈ F + gilt, so wird zur Gewährleistung der Verlustlosigkeit
noch das folgende Schema als “Datenbankschlüssel” hinzugenommen.
R3 = (KN, ∅)
Als zweites wollen wir das Schema R = (R, F ) aus dem Beispiel 6.10 betrachten.
R = {(S)tudent, (F )ach, (D)ozent}
F = {SF → D, D → F }
Der Synthesealgorithmus wird die beiden Relationen
R1 = (SF D, {SF → D, D → F })
R2 = (DF, {D → F })
erzeugen. Die zweite Relation ist aber offensichtlich redundant und kann aus dem Schema
entfernt werden. Das heisst, wie erwartet verletzt das Schema weiterhin BCNF. Dies kann
nicht anders sein, da es keine unabhängige Zerlegung dieses Schemas gibt.
6.6
Mehrwertige Abhängigkeiten und 4NF
In diesem Abschnitt stellen wir noch einen weiteren Typ von lokalen Integritätsbedingen
vor, die für die weitere Normalisierung von Relationen von Bedeutung sind. Zur Motivation
betrachten wir das folgende Beispiel.
Beispiel 6.15 [Kurs-Dozent-Buch Tabelle] Die folgende Relation enthält Informationen
über Kurse, Dozenten und Lehrbücher.
Kurs-Dozent-Buch-Tabelle
Kurs
Dozent Buch
Physik
Green
Einführung Mechanik
Physik
Green
Optik
Physik
Brown Einführung Mechanik
Physik
Brown Optik
Mathematik Green
Einführung Mechanik
Mathematik Green
Vektoranalyse
Mathematik Green
Trigonometrie
6-20
Ausser den trivialen FDs erfüllt diese Relation keine weiteren funktionalen Abhängigkeiten.
Dennoch stellt man fest,dass hier eine Abhängigkeit zwischen den Datenwerten besteht: Jeder
Kurs bestimmt eine Menge von Dozenten, die den Kurs geben und eine Menge von Büchern,
die im Kurs verwendet werden. Zwischen den Dozenten und den Büchern besteht aber keine
Beziehung. Insbesondere enthält die Relation gewisse Redundanzen.
Da keine nichttrivialen FDs existieren, ist die Tabelle in BCNF und der einzige Schlüssel ist
K = {Kurs, Dozent, Buch}
Die Beobachtungen aus dem Beispiel 6.15 formalisieren wir wie folgt:
Definition 6.16 [Mehrwertige Abhängigkeit] Es sei V eine Attributmenge,
X, Y ⊆ V und Z = V \ X ∪ Y . Eine mehrwertige Abhängigkeit (multivalued
dependency, kurz MVD) X →→ Y bezeichnet die folgende semantische Bedingung:
Sei r ∈ REL(V )


 true
falls (∀t1 , t2 ∈ r)(t1 (X) = t2 (X) =⇒
(∃t3 ∈ r)(t3 (X) = t1 (X) ∧ t3 (Y ) = t1 (Y ) ∧ t3 (Z) = t2 (Z)))
(X →→ Y )(r) :=

 f alse sonst
Aus Symmetriegründen folgt sofort, dass wenn r die MVD X →→ Y erfüllt, ein weiteres
Tupel t4 existiert mit t4 (X) = t1 (X) ∧ t4 (Y ) = t2 (Y ) ∧ t4 (Z) = t1 (Z).
Intuitiv besagt also eine MVD X →→ Y , dass jeder X-Wert eine Menge von Y-Werten
bestimmt, dass andererseits jedoch kein Zusammenhang zwischen diesen Y-Werten und den
Wert der restlichen Attribute V \ X ∪ Y besteht.
Im Beispiel 6.15 gelten die beiden folgenden MVDs:
Kurs →→ Dozent und
Kurs →→ Buch
Beispiel 6.16 [Beispiele zur Definition] In unserem Beispiel gilt: X = Kurs, Y =
Dozent und Z = Buch. Wir nehmen nun die beiden Tupel:
t1 = (P hysik, Green, Einf ührungM echanik) und
t2 = (P hysik, Brown, Optik)
aus der Definition können wir nun schliessen, dass ein Tupel
t3 = (P hysik, Green, Optik)
in der Relation exitiert. Dies ist aber auch tatsächlich der Fall.
Die Gültigkeit einer MVD lässt sich auch wie folgt charakterisieren:
Satz 6.12 [MVD] Es seien X, Y ⊆ V, Z := V \ Y . Dann gilt:
(∀r ∈ REL(V ))((X →→ Y ) = true ⇐⇒ r = πXY (r) ⊲⊳ πXZ (r))
Beweis:
=⇒: r erfülle X →→ Y . Da klar ist, dass r ⊆ πXY (r) ⊲⊳ πXZ (r) genügt es zu
zeigen, dass
r ⊇ πXY (r) ⊲⊳ πXW (r)
6-21
Dabei sei W = V \ XY (beachte: XZ = XW ). Sei nun t ∈ πXY (r) ⊲⊳ πXW (r).
Dann existieren t1 ∈ πXY und t2 ∈ πXW mit
t(XY ) = t1 , t(XW ) = t2
Hieraus folgt t(X) = t1 (X) = t2 (X), t(Y ) = t1 (Y ) und t(W ) = t2 (W ). Dann
gibt es weiter Tupel u1 , u2 ∈ r mit
t1 = u1 (XY ), t2 = u2 (XW )
und daher
t(X) = u1 (X) = u2 (X) ∧ t(Y ) = u1 (Y ) ∧ t(W ) = u2 (W )
Aus der Gültigkeit von X →→ Y in r folgt nun t ∈ r mit Definition 6.16
⇐=: Seien t1 , t2 ∈ r mit t1 (X) = t2 (X). Dann existieren u1 ∈ πXY (r)mitu1 =
t1 (XY ) und u2 ∈ πXW (r)mitu2 = t1 (XW ). Wegen r = πXY (r) ⊲⊳ πXZ (r) existiert
t3 ∈ r mit t3 (XY ) = t1 (XY ), t3 (XW ) = t2 (XW ) also folgt:
t3 (X) = t1 (X) ∧ t3 (Y ) = t1 (Y ) ∧ t3 (W ) = t2 (W )
somit gilt X →→ Y in r.
Wie für FDs können wir für MVDs den Implikationsbegriff (|=) und den Ableitungsbegriff
(⊢) einführen.
Definition 6.17 [Implikation MVD] Sei M eine MVD-Menge über die Attributmenge R und m eine MVD mit attr(m) ⊆ R. M impliziert m, kurz M |= m,
falls SATR (M ) ⊆ SATR (m) gilt.
MVDs lassen sich mit dem folgenden vollständigen und korrekten Regelsystem ableiten:
Satz 6.13 [Ableitungsregeln für MVDs] Sei V eine Attributmenge und X, Y, Z, W ⊆
V dann gilt:
(M0) X →→ Y =⇒ X →→ V \ Y (Komplement-Regel)
(M1) Y ⊆ X =⇒ X →→ Y (Reflexivität)
(M2) Z ⊆ W ∧ X →→ Y =⇒ XW →→ Y Z (Erweiterung)
(M3) X →→ Y ∧ Y →→ Z −→ X →→ Z \ Y
Schliesslich kann man auch für gemischte Abhängigkeitsmengen (FDs und MVDs) Implikation
bzw. Ableitung betrachten.
Satz 6.14 [Gemischte Ableitungsregeln für MVDs und FDs] Sei V eine
Attributmenge und X, Y, S, T ⊆ V dann gilt:
(FM1) X → Y =⇒ X →→ Y
(FM2) X →→ Y ∧ S → T ∧ S ∩ Y = ∅ =⇒ X → Y ∩ T
Jede FD kann also insbesonder als MVD aufgefasst werden. Eine MVD und eine FD implizieren unter Umständen eine neue FD.
6-22
Satz 6.15 [MVD und FD Korrektheit und Vollständigkeit] Sei Σ = F ∪ M
eine Menge von FDs und MVDs. Dann ist die Menge {A1, A2, A3, M 0, M 1, M 2,
M 3, F M 1, F M 2} von Axiomen vollständig und korrekt. Das heisst, jede Abhängigkeit
σ ∈ Σ+ ist unter verwendung dieser Axiome aus Σ ableitbar.
Als letztes wollen wir noch definieren, wann eine Relation in 4NF ist. Zuerst aber der Begriff
der trivialen MVD
Definition 6.18 [Triviale MVD] Eine MVD X →→ Y über eine Attributmenge
U heisst trivial, falls entweder Y ⊆ X oder X ∪ Y = U .
Definition 6.19 [4NF] Sei R = (U, F ∪ M ). R ist in vierter Normalform (4NF)
falls für jede von F ∪ M implizierte nicht triviale MVD der Form X →→ Y gilt,
dass X ein Superschlüssel von R ist.
Beispiel 6.17 [Zerlegung in 4NF] Wir betrachten die Relation aus dem Beispiel 6.15.
In diesem Beispiel gelten die MVDs Kurs →→ Dozent und Kurs →→ Buch. Um diese zu
eliminieren wird die Relation nach Satz 6.12 in die beiden Relationen
R1 = ({Kurs, Dozent}, {Kurs →→ Dozent}) und
R2 = ({Kurs, Buch}, {Kurs →→ Buch})
zerlegt.
Kurs_Dozent_4N F
Kurs
Dozent
Physik
Green
Physik
Brown
Mathematik Green
Kurs_Buch_4N F
Kurs
Buch
Physik
Einführung Mechanik
Physik
Optik
Mathematik Einführung Mechanik
Mathematik Vektoranalyse
Mathematik Trigonometrie
Die Relationen R1 und R2 enthalten jetzt nur noch triviale MVDs und sind daher in 4NF
6-23
6-24
Kapitel 7
Vom ERM zum Datenbankschema
7.1
Datendefinitionssprache (SQL/DDL)
Die Datendefinitionssprache (kurz DDL für Data Definition Language) dient dazu, das Datenbankschema zu definieren. Dazu gehören die bekannten Konzepte Attribut, Domäne, Relationenschema, Schlüssel, Primärschlüssel, Fremdschlüssel und Integritätsbedingung.
7.1.1
Datentypen
Datentypen werden bei der Definition von Attributen (und Domänen) verwendet. Die im
Standard geforderten Datentypen sind in der nachfolgenden Liste angegegeben.
Typ
BOOLEAN
SMALLINT
Instanz
Wahrheitswert
ganze Zahl
Beispielwert
TRUE
4771
Festkommazahl
1003.65
Fliesskommazahl
1.5E-4
alphanumerische
Zeichenkette
’Ein String’
binäre
Zeichenkette
B’110100101’
Datum
Zeit
Zeitstempel
Zeitintervall
DATE’1951-11-24’
TIME’11:39:49’
TIMESTAMP’2002-08-23 14:15:00’
INTERVAL’48’ HOUR
INTEGER
BIGINT
DECIMAL(p,q)
NUMERIC(p,q)
FLOAT(p)
REAL
DOUBLE PRECISION
CHAR(q)
VARCHAR(q)
CLOB
BINARY(q)
BINARY VARYING(q)
BLOB
DATE
TIME
TIMESTAMP
INTERVAL
7-1
7.1.2
Domänen
Domänen stellen benutzerdefinierte Wertebereiche dar. Sie basieren auf bereits existierenden
Datentypen, die selbst wiederum benutzerdefiniert sein können.
7.1.2.1
Kreieren einer neuen Domäne
<domain>
::=
<domain-name>
<data-type>
[<default-definition>]
[<domain-constraint>]...
<default-definition> ::= DEFAULT {<literal> | <niladic-function> | NULL}
niladic-function
::= {USER | CURRENT_USER | SESSION_USER |
SYSTEM_USER | CURRENT_DATE |
CURRENT_TIME | CURRENT_TIMESTAMP}
CREATE DOMAIN
Die Definition von <domain-constraint> werden wir im Abschnitt 7.1.4 behandeln.
Beispiel 7.1 [Neue Domäne]
CREATE DOMAIN
nametyp
VARCHAR(30) DEFAULT
’???’
Die Elemente der definierten Domäne sind alle Strings mit einer maximalen Länge von 30.
Wird auf einem Attribut mit dieser Domäne kein expliziter Wert erfasst, so wird der Default
(in diesem Fall ’???’) angenommen.
CREATE DOMAIN
visumtyp
CHAR(15) DEFAULT CURRENT_USER
In diesem Beispiel wird als Default der Name des Benutzers angenommen.
7.1.2.2
Ändern einer Domäne
Die Definition einer Domäne kann mit dem ALTER DOMAIN Befehl verändert werden. Dabei
ist es aber nicht möglich, den der Domäne zugrundeliegende Datentyp zu verändern.
<domain-alteration> ::=
ALTER DOMAIN
{
<domain-name>
DROP DEFAULT
|
|
|
<default-definition>
<domain-constraint>
DROP CONSTRAINT <constraint-name>}
SET
ADD
Bemerkung 7.1 [Löschen des Defaults] Bevor der Default der Domäne gelöscht wird
(mit DROP DEFAULT), wird dieser zuerst auf allen Attributen kopiert, welche mit Hilfe dieser
Domäne definiert sind.
7-2
7.1.2.3
Löschen einer Domäne
Eine Domäne kann mit dem DROP DOMAIN Befehl wieder gelöscht werden.
<drop-domain> ::=
DROP DOMAIN
<domain-name>
[RESTRICT | CASCADE]
Falls RESTRICT angegeben ist und die Domäne in einer anderen Definition verwendet wird,
so wird die Domäne nicht gelöscht. Falls CASCADE angegeben wird, so werden Views oder Integritätsbedingungen, die diese Domäne referenzieren, gelöscht. Attribute, welche die Domäne
referenzieren, werden nicht gelöscht. Statt dessen wird die Definition der Domäne auf den
entsprechenden Attributen kopiert. Der Default ist RESTRICT.
7.1.3
Relationenschema
Ein Relationenschema wird durch die Angabe aller zugehörigen Attribute definiert. Zu jedem
Attribut muss eine Domäne oder ein Datentyp angegeben werden.
7.1.3.1
Kreieren eines neuen Relationenschemas
<table-definition>
::=
CREATE TABLE <table-name>
(<table-element> [,<table-element>]. . . )
<table-element>
::= <attribut-definition> | <table-constraint>
<attribut-definition> ::= <attribut-name>
{<data-typ> | <domain>}
[<default-definition>]
[<attribut-constraint>]. . .
Die Definition von <table-constraint> und <attribut-constraint> werden wir im Abschnitt
7.1.4 behandeln.
<default-definition> hat dieselbe Bedeutung wie für Domänen. Ist für ein Attribut sowohl auf
der Domäne wie auch auf dem Attribut ein Default definiert, so hat die Definition auf dem
Attribut den Vorrang.
Beispiel 7.2 [Neues Relationenschema] Wir definieren nun das Relationenschema für den
Lieferanten aus unserem Beispiel.
lieferant (
INTEGER,
nametyp,
CREATE TABLE
l_nr
name
ort
plz
VARCHAR(30),
CHAR(8)
)
Dabei ist nametyp die Domäne, die im Beispiel 7.1 definiert wurde.
7-3
7.1.3.2
Ändern eines Relationenschemas
Ein Relationenschema kann mit dem ALTER TABLE Befehl verändert werden. Was nicht
möglich ist, ist die Domäne (oder den Datentyp) eines Attributs zu verändern.
<table-alteration> ::=
<action>
::=
|
|
|
|
Der
DROP [COLUMN]
<table-name> <action>
ADD [COLUMN] <attribut-definition>
ALTER [COLUMN] <attribut-name>
{SET <default-definition> | DROP DEFAULT}
DROP [COLUMN] <attribut-name> [RESTRICT |
ADD <table-constraint>
DROP CONSTRAINT <constraint-name>
[RESTRICT | CASCADE]
ALTER TABLE
CASCADE]
Befehl wird in den beiden folgenden Fällen nicht akzeptiert:
• Das Attribut ist das letzte Attribut im Relationenschema
• Im Befehl wird RESTRICT angegeben und das Attribut wird in einer View- oder ConstraintDefinition referenziert. Wird hingegen CASCADE angegeben, so werden alle Views und
Constraints, die das Attribut referenzieren, auch gelöscht.
Beispiel 7.3 [Ändern des Schemas] In der Definition des Lieferanten wollen wir das neue
Attribut “strasse” einführen. In allen existierenden Tupeln soll der Wert auf “nicht erfasst”
gesetzt werden.
ALTER TABLE
lieferant
ADD
strasse
VARCHAR(30) DEFAULT
’nicht erfasst’
Nun entfernen wir noch das Attribut “plz” aus dem Relationenschema
ALTER TABLE
lieferant
DROP
plz
RESTRICT
Änderungen im Schema sind auch möglich, wenn schon Tupel in der entsprechenden Relation
gespeichert sind.
7.1.3.3
Löschen eines Relationenschemas
Ein Relationenschema kann mit dem DROP TABLE Befehl aus der Datenbank entfernt werden.
Mit diesem Befehl werden auch alle Tupel in der entsprechenden Relation gelöscht.
<drop-table> ::=
DROP TABLE
<table-name> [RESTRICT |
CASCADE]
Falls RESTRICT angegeben wird und das Relationenschema in einer View- oder ConstraintDefinition verwendet wird, so wird die Operation nicht durchgeführt.
Beispiel 7.4 [Löschen eines Schemas] Das Relationenschema für Lieferanten (und die
entsprechende Relation) werden aus der Datenbank entfernt.
DROP TABLE
lieferant
CASCADE
7-4
7.1.4
Integritätsbedingungen (Constraint)
Integritätsbedingungen dienen dem Ausschluss von “semantisch inkorrekten” Datenzuständen.
SQL:1992 unterstützt die deklarative Formulierung von Integritätsbedingungen auf Domänen, Attributten und Relationenschematas. Zusätzlich sind allgemeine Integritätsbedingungen
genannt Assertion möglich.
Alle erwähnten Integritätsbedingungen haben einen Namen. Dieser wird bei einer Verletzung
der Bedingung in der entsprechenden Fehlermeldung ausgegeben. Falls der Name nicht explizit angegeben wird, so kreiert das DBMS automatisch einen eindeutigen Namen. Damit
die Fehlermeldungen lesbarer werden ist es von Vorteil, den Namen für eine Bedingung selber
festzulegen. Die Syntax ist die folgende:
<constraint>
::=
<constraint-type> ::=
7.1.4.1
[CONSTRAINT <constraint-name>] <constraint-type>
<domain-constraint> | <attribut-constraint>
| <table-constraint>
Integritätsbedingungen auf Domänen
Jede Domänenbedingung wird durch eine Check-Klausel ausgedrückt:
<domain-constraint> ::=
CHECK
(<conditional-expression>)
Der boolsche Ausdruck kann das spezielle Wortsymbol VALUE als Platzhalter für einen beliebiegen Wert aus dem Wertebereich enthalten. Der Ausdruck darf die Operatoren OR, AND
und NOT enthalten.
Beispiel 7.5 [Integritätsbedingung auf Domäne]
CREATE DOMAIN
DEFAULT
CHECK
7.1.4.2
Jobs VARCHAR(12)
’Angestellter’
(VALUE IN (’Angestellter’, ’Manager’))
Integritätsbedingungen auf Attributen
Diese Integritätsbedingungen werden bei der Definition von Attributen angegeben. Wie der
Name andeutet, sind solche Bedingungen genau auf einem Attribut in einem Relationenschema
beschränkt. Eine Attributbedingung hat die folgende Form:
<attribut-constraint> ::=
NOT NULL
|
|
|
UNIQUE
PRIMARY KEY
<table-name(attribut-name)>
[ON DELETE <referential-action>]
[ON UPDATE <referential-action>]
| CHECK(<conditional-expression>)
REFERENCES
7-5
Zu dieser Definition nun die folgenden Erklärungen:
•
NOT NULL
schliesst den Wert
•
UNIQUE
•
PRIMARY KEY
•
REFERENCES spezifiziert
•
CHECK schränkt den Wertebereich der Spalte durch die Angabe eines boolschen Ausdrucks ein. Es gilt jedoch, dass der Ausdruck nur eine Variable enthalten darf und zwar
das Attribut selbst. Der Ausdruck darf die Operatoren OR, AND und NOT enthalten.
NULL
für dieses Attribut aus.
definiert einen Schlüsselkandidaten und schliesst somit aus, dass zwei verschiedene Tupel denselben Wert für dieses Attribut besitzen. Die einzige Ausnahme
ist der Wert NULL, der beliebig oft vorkommen darf.
bestimmt das Attribut als Primärschlüssel der Relation. Diese Angabe
impliziert gleichzeitig eine Not-Null-Bedingung. Ferner darf es pro Relation nur einen
Primärschlüssel geben.
das Attribut als Fremdschlüssel. Jede Fremdschlüssel-Bedingung
wird implizit oder explizit mit einer referenziellen Aktion assoziert (siehe Abschnitt “Integritätsbedingungen auf Relationenschematas”).
Beispiel 7.6 [Integritätsbedingung auf ein Attribut]
CREATE TABLE
p_nr
name
vorname
ahv_nummer
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
CHAR(14) UNIQUE
)
CREATE TABLE
p_nr
phobby (
INTEGER REFERENCES
person (p_nr)
ON DELETE CASCADE,
hobby
VARCHAR(20) NOT NULL
(hobby
hobby)
CHECK
PRIMARY KEY(p_nr,
IN
(’Golf’, ’Theater’, ’Fussball’))
)
7.1.4.3
Integritätsbedingung für Relationenschematas
Diese Integritätsbedingungen werden bei der Definition eines Relationenschemas angegeben.
Die Integritätsbedingungen haben die folgende Form:
7-6
{PRIMARY KEY | UNIQUE} (<attr-list>)
| FOREING KEY (<attribut-list>)
REFERENCES table-name [(<attr-list>)]
[MATCH {SIMPLE | PARTIAL | FULL}]
[ON DELETE <referential-action>]
[ON UPDATE <referential-action>]
| CHECK (conditional-expression)
<attr-list>
::= <attribut-name> [,<attribut-name>]...
<referential-action> ::= {NO ACTION | CASCADE | SET DEFAULT |
<table-constraint>
::=
SET NULL}
Zu dieser Definition nun die folgenden Erklärungen:
•
definiert einen Schlüsselkandidaten und schliesst somit aus, dass zwei verschiedene Tupel denselben Wert für die angegebne Attributkombination besitzt. Auch
hier gilt die Ausnahme für den Wert NULL. Eine Attributkombination bei der mindestens
ein Attribut den Wert NULL hat, wird bei der Auswertung der Unique-Bedingung nicht
berücksichtigt.
•
bestimmt die Attributkombination als Primärschlüssel der Relation.
Diese Angabe impliziert gleichzeitig eine Not-Null-Bedingung. Ferner darf es pro Relation nur einen Primärschlüssel geben.
•
spezifiziert die Attributkombination als Fremdschlüssel und garantiert
die referentielle Integrität. Ferner können die Match-Regeln und die referenziellen Aktionen angegeben werden.
UNIQUE
PRIMARY KEY
FOREIGN KEY
Match-Regeln ermöglichen eine genauere Spezifikation des Verhaltens bei Auftreten
von Nullwerten.
MATCH SIMPLE Ist der Wert eines Attributs des Fremdschlüssels NULL, so wird
die Fremdschlüsselbedingung für dieses Tuple nicht überprüft. Dies ist die Defaultregel.
MATCH PARTIAL Entweder haben alle Attribute den Wert NULL oder jeder NichtNullwert stimmt mit dem korespondierenden Schlüsselwert eines Tupels in der
referenzierten Relation überein.
MATCH FULL Entweder haben alle Attribute den Wert NULL oder es gibt ein Tupel
in der referenzierten Relation, welches dieselben Werte für die korespondierenden Schlüsselwerte hat.
Referenzielle Aktionen geben an, was passieren soll, falls eine Zeile in der referenzierten Relation gelöscht wird (ON DELETE) oder der Schlüsselwert geändert wird
(ON UPDATE). Folgende referenzielle Aktionen sind spezifizierbar:
NO ACTION Das Löschen bzw. Ändern in der referenzierten Tabelle wird zurückgewiesen, wenn dadurch die Fremdschlüsselbedingung verletzt wird. Dies ist
die Defaulteinstellung.
CASCADE Das Löschen bzw. Ändern wird kaskadierend auf allen Tupeln durchgeführt, welche das gelöschte bzw. das geänderte Tupel referenzieren.
SET NULL Felder des Fremdschlüssels werden auf Null gesetzt.
SET DEFAULT Der Fremdschlüssel wird auf den definierten Defautwert gesetzt,
falls in der referenzierten Relation ein Tupel mit dem entsprechenden Schlüssel
gibt. Ist keine solche Zeile vorhanden, so wird die Lösch- bzw. Änderungsanweisung zurückgewiesen.
7-7
•
formuliert eine allgemeine Integritätsbedingung, wobei das Prädikat Subqueries
enthalten darf. Komplexere Bedingungen können mit den boolschen Operatoren OR,
AND und NOT gebildet werden.
CHECK
Beispiel 7.7 [Integritätsbedingungen auf das Schema]
CREATE TABLE
p_nr
name
vorname
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(20) NOT NULL,
UNIQUE(name,
vorname)
);
CREATE TABLE
name
vorname
hobby
hobbys (
VARCHAR(30),
VARCHAR(20),
VARCHAR(20) NOT NULL,
PRIMARY KEY
FOREIGN KEY
(name, vorname, hobby),
(name, vorname) REFERENCES person (name, vorname)
ON DELETE CASCADE
ON UPDATE CASCADE
CHECK
(hobby
IN
(’Golf’, ’Theater’, ’Boxen’, ’Klavierspielen’))
)
Beispiel 7.8 [Namen von Integritätsbedingungen]
Im nachfolgenden Beispiel werden die Namen der Integritätsbedingungen noch explizit angegeben.
Dies kann bei der Ausgabe von Fehlermeldungen von Vorteil sein.
CREATE TABLE
p_nr
person (
INTEGER,
"Primaerschluessel Person"
VARCHAR(30) NOT NULL,
VARCHAR(20) NOT NULL,
CONSTRAINT
name
vorname
CONSTRAINT
UNIQUE(name,
PRIMARY KEY
"Name und Vorname einer Person eindeutig"
vorname)
);
7.1.4.4
Allgemeine Integritätsbedingungen (ASSERTION)
Eine Assertion ist eine Integritätsbedingung, die ausserhalb einer Relationenschemadefinition
mit der folgenden Anweisung angelegt wird:
<assertion> ::=
<assertion-name>
(<conditional-expression>)
CREATE ASSERTION
CHECK
7-8
Die conditional-expression ist ein beliebiger SQL-Befehl, der als wahr oder falsch evaluiert
wird. Im Ausdruck können beliebige Tabellen miteinander verknüpft werden. Dies ermöglicht
die Definition allgemeiner Integritätsbedingungen über mehrere Tabellen hinweg.
Beispiel 7.9 [Assertions]
Mit Hilfe von Assertions können wir Bedingungen erzwingen, die mehrere Tabellen betreffen.
Im Personenbeispiel können wir so erzwingen, dass jede Person mindestens 1 Hobby besitzt.
Die Assertion würde etwa so aussehen.
CREATE ASSERTION
personhobby
CHECK (NOT EXISTS
(SELECT *
FROM
person p
WHERE NOT EXISTS
(SELECT *
FROM
pers_hobby ph
WHERE p.p_nr = ph.p_nr)))
7.1.4.5
Überprüfungsmodi für Integritätsbedingungen
Jede Integritätsbedingung hat einen initialen Überprüfungsmodus und einen Verzögerungsmodus.
• Der Überprüfungsmodus bestimmt innerhalb einer laufenden Transaktion den relativen
Zeitpunkt, an dem die Integritätsbedingung überprüft wird. Zwei Modi werden unterschieden:
1.
IMMEDIATE
2.
DEFERRED
führt die Überprüfung unmittelbar nach jeder SQL-Anweisung durch.
verzögert die Überprüfung an das Ende der jeweiligen Transaktion.
Der initiale Überprüfungsmodus ist die Einstellung, die zu Beginn jeder Transaktion
gilt. Dieser Modus kann bei der Definition der Integritätsbedingung explizit durch die
Klausel INITIALLY IMMEDIATE bzw. INITIALLY DEFERRED gesetzt werden.
• Der Modus DEFERRED können nur verzögerbare Integritätsbedingungen annehmen. Die
Verzögerbarkeit wird entweder implizit durch die Klausel INITIALLY DEFERRED oder
explizit durch die Klausel DEFERRABLE gesetzt.
Zusammengefasst gilt die folgende Klausel für die Spezifikation von Integritätsbedingungen:
[CONSTRAINT <constraint-name>] <constraint-definition>
[[NOT] DEFERRABLE]
[INITIALLY {IMMEDIATE | DEFERRED}]
Die Defaulteinstellungen sind
NOT DEFERRABLE
und
INITIALLY IMMEDIATE.
Der Verzögerungsmodus kann für verzögerbare Integritätsbedingungen innerhalb einer Transaktion verändert werden. Dies geschieht mit dem folgenden Statement:
<setconstraint> ::=
<name-list>
::=
<name-list> {DEFERRED | IMMEDIAT}
| <constraint-name> [, <constraint-name>]...
SET CONSTRAINTS
ALL
7-9
Es gibt viele Beispiele, in denen die überprüfung einer Integritätsbedingung verzögert werden
muss. Im Beispiel 7.9 kann keine Person eingefügt werden, weil zu diesem Zeitpunkt zur Person
noch kein Hobby existiert. Dieses kann aber wegen der referenziellen Integrität nicht vor der
Person eingefügt werden. Das heisst, das Problem ist nur lösbar, wenn wir die Überprüfung
der Assertion bis zum Ende der Transaktion verzögern.
Die Definition der Assertion muss also folgendermassen aussehen:
CREATE ASSERTION
personhobby
CHECK(NOT EXISTS
(SELECT *
person p
FROM
WHERE NOT EXISTS
(SELECT *
pers_hobby ph
WHERE p.p_nr = ph.p_nr)))
FROM
DEFERRABLE INITIALLY DEFERRED
7.2
ERD und Datenbankschema
Im Kapitel 2 wurde die Realität mittels Konstruktionselementen in einem ERD abgebildet.
Es ging vor allem darum, diese Abbildung, in einer dem menschlichen Verständnis möglichst
entgegenkommender Weise darzustellen. In diesem Kapitel wird gezeigt, wie ein ERD in ein
voll normalisiertes relationales Datenbankschema übersetzt werden kann. Dabei ist es wichtig,
dass das entstehende Datenbankschema die folgenden Eigenschaften aufweist.
1. Alle im ERD definierten Beziehungen müssen im relationalen Datenbankschema abgebildet sein.
2. Alle Relationen sind in 3NF
3. Die Anzahl Relationenschematas ist minimal.
Glücklicherweise ist die übersetzung vom ER-Modell zum relationalen Modell sehr einfach und
kann mit Hilfe eines einfachen Algorithmus vorgenommen werden. Dabei wird so vorgegangen,
dass für jedes Konstruktionselement im ERD eine Tabelle oder ein Attribut im relationalen
Modell erstellt wird.
7.2.1
Entitätsmengen und Attributte
Eine Entität ist durch den Entitätsschlüssel eindeutig bestimmt. Daher konstruieren wir für
jede Entitätsmenge ein Relationsschema. Der Name des Relationsschema ist identisch mit dem
Namen der entsprechenden Entitätsmenge. Der Entitätsschlüssel wird zum Primärschlüssel. In
der Abbildung 7-1 ist die Entitätsmenge person mit den entsprechenden Attributen dargestellt.
Im folgenden sind die Schritte zum erstellen eines entsprechenden Schemas angegeben:
1. Kreieren einer Tabelle mit dem Namen der Entitätsmenge und dem Entitätsschlüssel
als primär Schlüssel.
7-10
<<entity>>
person
p_nr
titel[0..1]
name
vorname
geburtsdatum
hobby[1..*]
{Entitykey}
Abbildung 7-1: Die Entitätsmenge Person
CREATE TABLE
p_nr
person (
INTEGER PRIMARY KEY
)
2. Nun können alle Attribute mit Kardinalität 1 einfach zum Schema hinzugefügt werden.
Wichtig, ist dass in diesem Fall die Klausel NOT NULL angegeben wird.
CREATE TABLE
person (
p_nr
name
vorname
geburtsdatum
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
DATE NOT NULL
)
3. Anschliessend werden alle Attribute mit Kardinalität C im Schema eingefügt. Für diese
Attribute sind aber auch Nullwerte erlaubt.
CREATE TABLE
person (
p_nr
titel
name
vorname
geburtsdatum
INTEGER PRIMARY KEY,
VARCHAR(15),
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
DATE NOT NULL
)
4. Attribute mit Kardinalität M oder MC müssen in ein eigenes Relationenschema ausgelagert werden. Der Primärschlüssel des neuen Schemas besteht aus dem Primärschlüssel
PK des ursprünglichen Schemas plus dem Wert des ausgelagerten Attributs. Zusätzlich
ist PK Fremdschlüssel auf das ursprüngliche Relationenschema.
In unserem Beispiel hat das Attribut hobby die Kardinalität M daher definieren wir das
folgende Relationenschema
CREATE TABLE
personhobby (
p_nr
INTEGER REFERENCES
person(p_nr)
ON DELETE CASCADE,
hobby
VARCHAR(20)
CHECK(hobby IN
(’Fussball’, ’Klavierspielen’, ’Theater’,
’Golf’, ’Boxen’, ’Kochen’, ’Gleitschirm’)),
PRIMARY KEY(p_nr, hobby)
)
7-11
Mit der obigen Konstruktion ist nur die Kardinalität MC abgedekt, da durch die Fremdschlüsselbedingung nicht erzwungen wird, dass jede Person ein Hobby besitzt. Will man
das erzwingen, so ist dies nur mit Hilfe einer globalen Integritätsbedingung möglich.
"Hobby Kardinalität M "
(NOT EXISTS (SELECT *
FROM
person LEFT JOIN personhobby
WHERE hobby IS NULL))
CREATE ASSERTION
CHECK
DEFERRABLE INITIALLY DEFERRED
Die Assertion kann erst am Ende der Transaktion getestet werden, da sonst ein Zyklus
mit der referentiellen Integrität entsteht.
7.2.2
Generalisierung und Spezialisierung
Die Generalisierung (bzw. Spezialisierung) von Entitätsmengen kann mit Hilfe von Überlagerungen im ERD dargestellt werden. Dabei sind die vier Fälle möglich, die im Kapitel 2 in
der Abbildung 2-5 dargestellt sind.
Für die Implementierung von Spezialisierungen stehen zwei Möglichkeiten zur Verfügung:
7.2.2.1
Vertikale Darstellung
Bei dieser Darstellungsart werden die Primärschlüssel der Relationenschemata, welche die
Spezialisierungen darstellen als Fremdschlüssel auf das Relationenschema, welches die Generalisierung darstellt definiert.
Beispiel 7.10 [Implementation der Generalisierung] Wir nehmen an, dass in unserem
System im ersten Schritt die folgenden drei Schematas definiert wurden:
CREATE TABLE
p_nr
name
vorname
geburtsdatum
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
DATE NOT NULL
)
CREATE TABLE
p_nr
spezialgebiet
arzt (
INTEGER PRIMARY KEY,
VARCHAR(30)
)
CREATE TABLE
p_nr
Krankenkasse
patient (
INTEGER PRIMARY KEY,
VARCHAR(30)
)
Nun möchten wir person als Generalisierung von arzt und patient darstellen. Dies können wir
tun, indem wir die entsprechenden Fremdschlüssel definieren:
7-12
CREATE TABLE
p_nr
arzt (
INTEGER PRIMARY KEY REFERENCES
person(p_nr)
ON DELETE CASCADE,
spezialgebiet
VARCHAR(30)
)
CREATE TABLE
p_nr
patient (
INTEGER PRIMARY KEY REFERENCES
person(p_nr)
ON DELETE CASCADE,
Krankenkasse
VARCHAR(30)
)
Man sieht sofort, dass diese Definitionen den Sachverhalt korrekt wiedergeben.
• Ein Arzt oder ein Patient kann nur einer Person entsprechen, da p_nr in arzt und
patient gleichzeitig Primär- und Fremdschlüssel ist.
• Die Entitäten arzt und patient sind von person abhängig, das heisst, Ärzte und Patienten
können nur existieren, falls sie auch als Person existieren.
Mit diesen Definitionen ist nur der Fall d) der Abbildung 2-5 abgedeckt. Die Fälle a) bis c)
können nicht mit Hilfe von Schlüsseln abgedeckt werden. Wir müssen zusätzlich noch folgende
Bedingungen erzwingen:
• Fälle a) und b):
Ein Element in Person darf entweder in Arzt oder Patient enthalten sein, aber nicht
in beiden (Disjunktheit). Dies ist eine globale Integritätsbedingung. Wir können also
schreiben:
CREATE ASSERTION
"Arzt und Patient disjunkt "
CHECK (NOT EXISTS(SELECT*
FROM
person
arzt
patient))
NATURAL JOIN
NATURAL JOIN
• Fälle a) und c):
Ein Element in Person muss entweder in Arzt oder in Patient auch vorhanden sein
(vollständige Überlagerung). Auch dies können wir als globale Integritätsbedingung ausdrücken:
CREATE ASSERTION
"person ist abstrakt"
CHECK (NOT EXISTS
(SELECT *
FROM
person p
WHERE NOT EXISTS
(SELECT * FROM arzt a
WHERE p.p_nr = a.p_nr)
AND
NOT EXISTS
(SELECT *
FROM
patient pa
WHERE p.p_nr = pa.p_nr)))
DEFERRABLE INITIALLY DEFERRED
7-13
Bemerkung 7.2 [Assertions] Heute existiert noch kein System, das ASSERTIONs unterstützt. Daher muss jede Applikation, welche die Relationen person, arzt und patient verändert,
mit geeigneten Routinen, Triggern und Check-Klauseln dafür sorgen, dass die entsprechenden
Bedingungen eingehalten werden.
7.2.2.2
Horizontale Darstellung
Bei dieser Darstellung werden die Attribute der Spezialisierung alle in das Relationenschema,
das die Generalisierung darstellt aufgenommen. Mit Zusätzlichen Attributten wird angezeigt,
um welche Spezialisierung es sich handelt.
Beispiel 7.11 [Horizontale Darstellung der Generalisierung] Falls wir wieder arzt und
patient zu person generalisieren wollen, so definieren wir nur ein Relationenschema mit dem
folgenden Aufbau.
CREATE TABLE
person (
p_nr
name
vorname
geburtsdatum
isarzt
spezialgebiet
ispatient
krankenkasse
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
DATE NOT NULL,
BOOLEAN NOT NULL,
VARCHAR(30),
BOOLEAN NOT NULL,
VARCHAR(30)
)
Falls arzt und patient disjunkt sind, so kann die folgende Constraint eingefügt werden:
person
"Arzt und Patient disjunkt"
(NOT(isarzt = TRUE AND ispatient = TRUE))
ALTER TABLE
ADD CONSTRAINT
CHECK
Falls arzt und patient die Entität person vollständig überlagern, so kann die folgende Constraint hinzugefügt werden:
person
ADD CONSTRAINT "person ist abstrakt"
CHECK (isarzt = TRUE OR ispatient = TRUE)
ALTER TABLE
7.2.3
Beziehungsmengen
In der Abbildung 7-2 sind die zwei wesentlichen Fälle für Beziehungsmengen angegeben (Die
Beziehungen können noch konditionell sein).
7-14
Arzt
{persistent}
a_nr
1
behandelt
1..*
Patient
{persistent}
pa_nr
a) Ein Arzt behandelt mehrere Patienten und ein Patient wird von genau einem Arzt behandelt.
Arzt
{persistent}
a_nr
1..*
behandelt
1..*
Patient
{persistent}
pa_nr
b) Ein Arzt behandelt mehrere Patienten und ein Patient wird von mehreren Aerzten behandelt.
Abbildung 7-2: Darstellung von Beziehungsmengen
7.2.3.1
Fall a) 1 zu M Beziehung
In diesem Fall können wir einfach im Relationenschema patient einen Fremdschlüssel auf das
Relationenschema arzt definieren
CREATE TABLE
a_nr
.
.
arzt (
INTEGER PRIMARY KEY
)
CREATE TABLE
pa_nr
a_nr
.
.
patient (
INTEGER PRIMARY KEY,
INTEGER NOT NULL REFERENCES
arzt(a_nr),
)
Erlauben wir nun, dass es Patienten gibt, die von keinem Arzt behandelt werden, so können
wir für den Fremdschlüssel Nullwerte zulassen.
CREATE TABLE
pa_nr
a_nr
.
.
patient (
INTEGER PRIMARY KEY,
INTEGER REFERENCES
arzt(a_nr),
)
In beiden Definitionen kann ein Arzt auch keine Patienten behandeln. Will man erzwingen,
dass ein Arzt mindestens einen Patienten behandelt, so kann man dies mit einer globalen
Integritätsbedingung erzwingen:
"Arzt hat mindestens 1 Patienten"
(NOT EXISTS (SELECT *
FROM
arzt LEFT JOIN patient
WHERE pa_nr IS NULL)
CREATE ASSERTION
CHECK
DEFERRABLE INITIALLY DEFERRED
7-15
7.2.3.2
Fall b) M zu M Beziehung
In diesem Fall führen wir ein neues Relationenschema ein, mit einem Fremdschlüssel auf arzt
und einem zweiten Fremdschlüssel auf patient. Beide Fremdschlüssel zusammen bilden dann
den Primärschlüssel der Relation.
CREATE TABLE
a_nr
.
.
arzt (
INTEGER PRIMARY KEY
)
CREATE TABLE
pa_nr
.
.
patient (
INTEGER PRIMARY KEY,
)
CREATE TABLE
a_nr
pa_nr
A_behandelt_P (
INTEGER REFERENCES
INTEGER REFERENCES
PRIMARY KEY
arzt(a_nr),
patient(pa_nr),
(a_nr, pa_nr)
)
Auch hier ist eigentlich der konditionelle Fall (MC:MC) dargestellt. Will man erzwingen, dass
ein Patient von mindestens einem Arzt behandelt wird, und/oder dass ein Arzt mindestens
einen Patienten behandelt, so müssen entsprechende Integritätsbedingungen definiert werden.
7.2.4
Beziehungsattribute
Die Darstellung von Beziehungsattributen hängt wieder von den Kardinalitäten der Beziehung
ab.
7.2.4.1
Fall a) 1 zu M Beziehung
In der Abbildung 7-3 sind die Diagnose und die Medikamente Beziehungsattribute, da diese
Information nur durch das Zustandekommen einer Beziehung zwischen einem Arzt und einem
Patienten Sinn machen. Da es sich hier um eine 1 zu M Beziehung handelt, kann das Attribut Diagnose zusammen mit dem Fremdschlüssel im Relationenschema patient aufgenommen werden. Da medikament die Kardinalität MC besitzt muss dieses Attribut in ein eigenes
Relationenschema ausgelagert werden.
Die entsprechenden Relationenschematas sehen also folgendermassen aus:
7-16
a_nr
Arzt
{persistent} 1
behandelt
1..*
Patient
{persistent}
pa_nr
ArztPatient
{persistent}
diagnose
medikament[0..*]
Abbildung 7-3: Beziehungsattribut bei einer 1 zu M Beziehung
CREATE TABLE
a_nr
.
.
arzt (
INTEGER PRIMARY KEY
)
CREATE TABLE
pa_nr
a_nr
diagnose
.
.
patient (
INTEGER PRIMARY KEY,
INTEGER REFERENCES
arzt(a_nr),
CLOB,
)
CREATE TABLE
pa_nr
medikamente (
INTEGER REFERENCES
patient(pa_nr)
ON DELETE CASCADE
medikamentVARCHAR(30),
PRIMARY KEY (pa_nr, medikament)
)
7.2.4.2
Fall b) M zu M Beziehung
In der Abbildung 7-4 ist die Kardinalität der Beziehung nun M zu M. In diesem Fall existiert aber schon die “Beziehungsrelation” ArztPatient und das Attribut diagnose kann in
diesem Relationenschema aufgenommen werden. Diese Darstellung ist korrekt, denn damit
können zwei verschiedene Ärzte für den gleichen Patienten auch verschiedene Diagnosen
stellen. Das Attribut medikament muss wieder in ein eigenes Relationenschema ausgelagert
werden. Der entsprechende Fremdschlüssel zeigt aber jetzt nicht auf den Patienten, sondern
auf die Beziehungsrelation.
<<entity>>
Arzt
1..*
behandelt
1..*
<<entity>>
Patient
<<entity>>
ArztPatient
Diagnose
Abbildung 7-4: Beziehungsattribut bei einer textbfM zu M Beziehung
7-17
Die entsprechenden Relationenschematas sehen also folgendermassen aus:
CREATE TABLE
arzt (
a_nr
.
.
INTEGER PRIMARY KEY
)
CREATE TABLE
patient (
pa_nr
.
.
INTEGER PRIMARY KEY,
)
CREATE TABLE
ArztPatient (
a_nr
pa_nr
diagnose
INTEGER REFERENCES
INTEGER REFERENCES
PRIMARY KEY
arzt(a_nr),
patient(pa_nr),
CLOB,
(a_nr, pa_nr)
)
CREATE TABLE
medikamente (
a_nr
pa_nr
medikament
PRIMARY KEY
INTEGER
INTEGER
VARCHAR(30),
(a_nr, pa_nr, medikament)
ArztPatient(a_nr,pa_nr)
FOREIGN KEY(a_nr,pa_nr) REFERENCES
ON DELETE CASCADE
)
7.2.5
Namenskonflikte
Bis jetzt hatten die Fremdschlüsselattribute immer denselben Namen wie die entsprechenden
Attribute des Primärschlüssels. Diese Regel kann nicht in jedem Fall eingehalten werden wie
das folgende Beispiel in der Abbildung 7-5 zeigt.
Produkt
{persistent}
Person
{persistent}
pr_nr
0..*
pe_nr
0..*
1
verkauft
produziert
Abbildung 7-5: Namenskonflikte
7-18
1
In diesem ERD haben wir zwischen Person und Produkt zwei verschiedene Beziehungsmengen.
Die erste drückt aus, dass eine Person mehrere Produkte herstellen kann. Die zweite Beziehung
sagt, dass eine Person mehrere Produkte verkaufen kann.
Nun haben wir nach unseren Regeln zweimal das Attribut pe_nr in der Relation Produkt und
dies kann natürlich nicht sein. In solchen Fällen müssen die beiden Attribute umbenannt werden. Falls wir für die Beziehung “wird verkauft” den Namen v_pe_nr und für die Beziehung
“wird produziert” den Namen p_pe_nr wählen, erhalten wir das folgende Schema:
Beispiel 7.12 [Namenskonflikt]
CREATE TABLE
pe_nr
CREATE TABLE
pr_nr
p_pe_nr
v_pe_nr
person (
INTEGER PRIMARY KEY
)
produkt (
INTEGER PRIMARY KEY,
INTEGER REFERENCES
INTEGER REFERENCES
person(pe_nr),
person(pe_nr)
)
7.2.6
Kontrolle der 3NF
In den vorigen Abschnitten haben wir gesehen, wie wir zu einem ERD schrittweise ein relationales Datenbankschema konstruieren können. Wir müssen nun noch überlegen, ob alle
Relationen im resultierenden Schema auch in 3NF sind.
7.2.6.1
Alle Schematas sind in 2NF
Dass alle Relationen in 2NF sind ist klar. In unserem ER-Modell ist der Entitätsschlüssel immer ein künstlicher Schlüssel, der nur aus einem Attribut besteht, so dass bei Entitätsmengen
keine Verletzung der 2NF möglich ist.
Die einzigen Relationenschematas mit zusammengestzten Schlüssel ergeben sich bei Mehrfachattributen und bei den Zwischenrelationen, die eine M zu M Beziehung darstellen.
1. Mehrfachattribute
In diesem Fall gehört der Wert des Attributs auch zum Schlüssel. D.h. eine Verletzung
der 2NF ist gar nicht möglich.
2. M zu M Beziehung
In diesem Fall sind die Attribute, die nicht zum Schlüssel gehören Beziehungsattribute.
Beziehungsattribute sind aber nach Definition von beiden beteiligten Entitäten abhängig. Das heisst aber, dass das Attribut vom Primärschlüssel voll funktional abhängig
ist.
Beispiel 7.13 [3NF un Beziehungsattribute] Wir betrachten als Beispiel das Attribut diagnose
CREATE TABLE
a_nr
.
arzt (
INTEGER PRIMARY KEY
7-19
.
)
CREATE TABLE
pa_nr
.
.
patient (
INTEGER PRIMARY KEY,
)
CREATE TABLE
a_nr
pa_nr
diagnose
A_behandelt_P (
INTEGER REFERENCES
INTEGER REFERENCES
arzt(a_nr),
patient(pa_nr),
CLOB
PRIMARY KEY
(a_nr, pa_nr)
)
Wäre das Attribut diagnose z.B. nur von pa_nr abhängig, so müsste es als ein Attribut
von Patient aufgenommen werden.
7.2.6.2
Verletzung der 3NF
Es gibt zwei Fälle, in denen eine Verletzung der 3NF vorkommen kann.
Lokale verletzung der 3NF Es ist natürlich möglich, dass beim Erstellen des ER-Modells
eine transitive Abhängigkeit nicht erkannt wurde. Das heisst, die funktionale Abhängigkeit
zwischen zwei Attributen wurde nicht dargestellt. In diesem Fall muss das betroffene Relationenschema nach den Regeln der Normalisierung zerlegt werden, damit die 3NF erfüllt
wird.
Beispiel 7.14 [Zerlegung des Schemas] Im folgenden Beispiel wird bemerkt, dass das
Attribut plz den Ort funktional bestimmt. In diesem Fall wird das Relationenschema person
zerlegt.
Ausgangslage:
CREATE TABLE
p_nr
name
vorname
plz
ort
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
CHAR(8) NOT NULL,
VARCHAR(30) NOT NULL
)
Schematas nach der Zerlegung:
CREATE TABLE
p_nr
name
vorname
o_nr
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
INTEGER REFERENCES
plzort(o_nr)
7-20
)
CREATE TABLE
o_nr
plz
ort
plzort (
INTEGER PRIMARY KEY,
CHAR(8) NOT NULL
UNIQUE,
VARCHAR(30) NOT NULL
)
Bemerkung 7.3 [Integritätsbedingung und 3NF] Es kann auch sein, dass im ERM
eine funktionale Abhängigkeit innerhalb einer Entitätsmenge durch eine Integritätsbedingung
festgehalten wird und dadurch eine transitive Abhängigkeit ensteht. In diesem Fall muss das
entsprechende Relationenschema auch nach den Regeln der Normalisierung zerlegt werden.
Globale Verletzung der 3NF Dieser Fall tritt dann auf, wenn in einem Relationenschema
eine transitive Abhängigkeit der Form:
p_nr → plz → ort
vorkommt. Im Gegensatz zum lokalen Fall wurde aber die funktionale Abhängigkeit zwischen
plz und ort erkannt und festgehalten. Das heisst, im System existiert ein Relationenschema
der Form
CREATE TABLE
plz
ort
.
.
plzort (
CHAR(8) PRIMARY KEY,
VARCHAR(30) NOT NULL,
)
das diese Tatsache festhält. Im Unterschied zum lokalen Fall muss in diesem Fall nicht mehr
zerlegt werden wie das folgende Beispiel zeigt.
Beispiel 7.15 [Globale Verletzung der 3NF] In diesem Beispiel ist ort von plz funktional
Abhängig, wie es das Schema plzort festhält.
CREATE TABLE
p_nr
name
vorname
plz
ort
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
CHAR(8) NOT NULL,
VARCHAR(30) NOT NULL
)
CREATE TABLE
plz
ort
.
plzort (
CHAR(8) PRIMARY KEY,
VARCHAR(30) NOT NULL
7-21
.
)
In diesem Fall müssen wir nichts zerlegen. Es gnügt einen Fremdschlüssel von plz in Person
nach plzort einzuführen und das Attribut ort im Schema Person zu streichen.
CREATE TABLE
p_nr
name
vorname
plz
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
CHAR(8) NOT NULL REFERENCES
plzort(plz)
)
Wir wollen noch überlegen wie man solche globale Verletzungen der 3NF findet. Dazu dienen
die beiden folgenden Bedingungen:
Ein Relationenschema R kommt für eine globale Verletzung der 3NF in Frage falls:
1. Das Relationenschema R hat einen Fremdschlüssel a auf ein anderes Relationenschema
ER.
2. Die Schematas R und ER haben ein zusätzliches identisches Attribut b.
7.3
Ein vollständiges Beispiel
Wir wollen in diesem Abschnitt noch ein vollständiges Beispiel durchspielen.
7.3.1
Problembeschreibung
In einem Produktionsbetrieb sollen die folgenden Regeln gelten:
1. Eine Person bedient mehrere Maschinen.
2. Eine Person produziert mehrere Produkte.
3. Eine Maschine wird von einer Person bedient.
4. Eine Maschine produziert verschiedene Produkte.
5. Die Herstellung eines Produkts erfordert eine Maschine.
6. Die Herstellung eines Produkts erfordert eine Person.
7. Jede Person hat einen Namen.
8. Jedes Produkt hat eine Bezeichnung.
9. Zu jeder Maschine wird das Datum jeder Wartung festgehalten. Eine Maschine wird am
selben Tag nur einmal gewartet.
10. Zur Herstellung eines Produktes werden kein oder mehrere andere Produkte verwendet.
Das ERD ist in der Abbildung 7-6 angegeben. Die Nummern der Beziehungen entsprechen
den einzelnen Regeln.
7-22
Produkt
{persistent}
pr_nr
bezeichnung
0..*
Maschine
{persistent}
m_nr
maschinentyp
wartung[0..*]
8
Person
{persistent}
9
7
pe_nr
name
10
0..*
1..*
1..*
5
verwendet
1
produziert
4
6
1..*
3
1
bedient
1
1
produziert
2
Abbildung 7-6: Das Personen-Produkt-Maschine Beispiel
7.3.2
7.3.2.1
Erstellen des Datenbankschemas
Entitätsmengen und Attributte
Für die Entitätsmengen und einfachen Attribute erhalten wir die folgenden Relationen:
CREATE TABLE
pe_nr
name
Person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL
)
CREATE TABLE
pr_nr
bezeichnung
Produkt (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL
)
CREATE TABLE
m_nr
maschinentyp
Maschine (
INTEGER PRIMARY KEY,
VARCHAR(40) NOT NULL
)
Für das mehhrfach Attribut wartung erhalten wir noch das folgende Schema:
CREATE TABLE
m_nr
wartung
MaschineWartung (
Maschine(m_nr)
ON DELETE CASCADE,
DATE,
INTEGER REFERENCES
PRIMARY KEY(m_nr,
wartung)
)
7.3.2.2
Generalisierung und Spezialisierung
In diesem Beispiel sind keine Generalisierungen vorhanden.
7-23
7.3.2.3
Beziehungsmengen
Wir müssen nun die Relationsschemata um die folgenden Fremdschlüssel ergänzen, welche
die drei 1 zu M Beziehungen (Person produziert Produkt, Maschine produziert Produkt und
Person bedient Maschine) darstellen.
CREATE TABLE
pr_nr
bezeichnung
m_nr
pe_nr
Produkt (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
INTEGER REFERENCES
INTEGER REFERENCES
Maschine(m_nr),
Person(pe_nr)
)
CREATE TABLE
m_nr
maschinentyp
pe_nr
Maschine (
INTEGER PRIMARY KEY,
VARCHAR(40) NOT NULL,
INTEGER REFERENCES
Person(pe_nr)
)
Zusätlich müssen wir für die MC zu MC Beziehung “verwendet” ein neues Schema einführen.
CREATE TABLE
pr_nr
Verwendung (
INTEGER REFERENCES
Produkt(p_nr)
ON DELETE CASCADE,
komponente
INTEGER REFERENCES
PRIMARY KEY(p_nr,
Produkt(p_nr),
komponente)
)
7.3.2.4
Beziehungsattribute
In diesem Beispiel hat es keine Beziehungsattributte.
7.3.2.5
Kontrolle der 3NF
Wir stellen fest, dass das Relationenschema Produkt einen Fremdschlüssel m_nr auf Maschine
enthält. Ferner enthalten beide Schematas Produkt und Maschine das Attribut pe_nr. Somit
sind beide Bedingungen für eine mögliche Verletzung der 3NF gegeben. In unserem Beispiel
verletzt das Relationenschema Produkt die 3NF tatsächlich, da sowohl in Maschine wie auch in
Produkt die funktionale Abhängigkeit “Maschine wird von einer Person bedient” festgehalten
ist. Das Attribut pe_nr in Produkt muss also entfernt werden und wir erhalten das folgende
relationale Datenbankschema:
CREATE TABLE
pe_nr
name
Person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL
)
7-24
CREATE TABLE
pr_nr
bezeichnung
m_nr
Produkt (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
INTEGER REFERENCES
Maschine(m_nr)
)
CREATE TABLE
m_nr
maschinentyp
pe_nr
Maschine (
INTEGER PRIMARY KEY,
VARCHAR(40) NOT NULL,
INTEGER REFERENCES
Person(pe_nr)
)
CREATE TABLE
m_nr
MaschineWartung (
INTEGER REFERENCES
Maschine(m_nr)
ON DELETE CASCADE,
wartung
DATE,
PRIMARY KEY(m_nr,
wartung)
)
Bei den Überlegungen zur Verletzung der 3NF ist es wichtig, dass die funktionale Abhängigkeit
pr_nr → pe_nr in beiden Relationen auch wirklich gültig ist. Das braucht nicht unbedingt
der Fall zu sein wie das folgende Beispiel zeigt.
In der Relation Produkt sei mit dem Attribut pe_nr die Person gemeint, die das Produkt
verkauft. In diesem Fall gilt in der Relation Produkt die funktionale Abhängigkeit m_nr →
pe_nr nicht und das Attribut pe_nr darf natürlich nicht aus der Relation Produkt entfernt
werden.
7-25
7.4
Übungen
Aufgabe 7.1 [Kurssystem] Im Abschnitt 2.4.5 wurde das in der Abbildung 7-7 dargestellte
ERD für ein Firmeninternes Kurssystem entwickelt.
<<entity>
Angestellter
a_nr
name
0..*
<<entity>
Kurstyp
setzt voraus
kt_nr
bezeichnung
0..*
{overlapping}
<<entity>
Student
1
<<entity>
Dozent
doziert
1
<<entity>
Klassenraum
kr_nr
plaetze
hat Typ
0..*
besucht
0..*
<<entity>
Kursangebot
0..*
0..*
ka_nr
kurstag[1..*] : Date 0..*
1
braucht
Abbildung 7-7: ERD zum Kurssystem
Aufgaben
1. Ergänzen Sie das ERD um Attribute, so dass die folgenden Aufgaben gelöst werden
können:
• Erstellen einer Liste aller Angestellten der Firma, die auch als Dozent tätig sind.
Die Liste soll Name, Vorname, Geburtsdatum und Funktion des Angestellten enthalten, sowie eine Liste aller dozierten Kurse.
• Erstellen einer Liste aller Angestellten der Firma, die einen oder mehrere Kurse
besucht haben. Die Liste soll Name, Vorname und Geburtsdatum des Mitarbeiters
enthalten sowie eine Liste aller schon besuchten Kurse mit Datum des Kurses und
erzielte Schlussnote.
• Erstellen einer Liste aller noch nicht durchgeführten Kursangebote. Die Liste soll
den Typ des Kurses, die Kursdaten, der Name des Dozenten, den Kursraum und
die aktuelle Anzahl Anmeldungen enthalten. Sind für einen Kurs mehr Mitarbeiter
angemeldet als im Kursraum Plätze frei sind, so soll der Kurs auf der Liste speziell
markiert werden.
• In jedem Kurs wird eine Schlussprüfung durchgeführt. Die Noten der Studenten
sollen pro Kurs festgehalten werden.
2. Übersetzen Sie das ergänzte ERD in ein voll normalisiertes relationales Datenbankschema.
7-26
Aufgabe 7.2 [Kunden und Fakturen] In der Abbildung 7-8 ist ein ERD gegeben, dass
die Beziehungen zwischen den Kunden, den Fakturen und den Artikeln einer Firma darstellt.
Ein Kunde hat mehere Fakturen, die aus einer Liste der gekauften Artikel (mit Angabe der
Menge) besteht. Kunden können auch Artikel testen und dazu mehrere Kommentare abgeben.
<<entity>>
Kunde
k_nr
Name
Adresse
1
hat
<<entity>>
Faktura
0..* f_nr
datum
totalbetrag
0..*
<<entity>>
Artikel
besteht aus
0..*
1..* a_nr
bezeichnung
preis
<<entity>>
ArtikelMenge
menge
0..*
testet
<<entity>>
KundenKommentar
Kommentar[1..*]
Abbildung 7-8: Kunden-Fakturen-Artikel Beispiel
Aufgabe
Übersetzen Sie das gegebene ERD in ein voll normalisiertes relationales Datenbankschema.
7-27
7-28
Kapitel 8
Routinen und Trigger
In diesem Kapitel wollen wir noch zwei Konzepte betrachten, die schon lange in den meisten
DBMS bekannt sind aber erst mit SQL99 in den Standard aufgenommen wurden. Dies sind
die Benutzerdefinierten-Routinen (auch stored procedure gennant) und die Triggers.
Bemerkung 8.1 [Standard] Da Routinen und Triggers sehr spät standardisiert wurden,
existieren zwischen dem Standard und den verschiedenen DBMS-Produkten oft syntaktische
(und semantische) Unterschiede. Wir werden hier den SQL-Standard (2003) kennen lernen.
8.1
Routinen (SQL-invoked routine)
Benutzerdefinierte-Routinen ermöglichen das Speichern und Ausführen von Programmcode in
der Datenbank. Der Begriff Benutzerdefinierte-Routine umfasst Prozeduren und Funktionen.
Bemerkung 8.2 [Methoden] Neben den Prozeduren und Funktionen existieren im Standard auch noch Methoden. Dies sind spezielle Funktionen, die an einen Benutzerdefinierten
Datentyp gebunden sind. Diese Begriffe gehören zur objektrelationalen Erweiterung des Standards und werden in diesem Kapitel nicht behandelt.
Jede Routine besteht aus einer Signatur und einem Rumpf. Die Signatur enthält den Namen
der Routine und falls vorhanden die Parameter. Im Unterschied zu einer Prozedur besitzt eine
Funktion einen Rückgabewert und kann auch überladen werden (overriding).
Prozeduren und Funktionen unterscheiden sich auch in ihrem Aufruf. Prozeduren werden mit
einer Call-Anweisung aufgerufen, eine Funktion dagegen als Teil eines Ausdrucks, beispielsweise in der Select-Klausel einer Anfrage.
Der Rumpf einer Routine enthält die Implementierung der Routine. Bei SQL-Routinen wird
der Rumpf in SQL implementiert. Hier können die prozeduralen Erweiterungen von SQL
(siehe 8.1.2) verwendet werden. Insbesondere auch die zusammengesetzte Anweisung, die
einem Blockstatement in der Sprache Java entspricht.
Bemerkung 8.3 [Externe Routinen] Der SQL-Standard lässt neben SQL-Routinen auch
sogenannte externe Routinen zu. Bei externen Routinen wird die Implementation in einer
anderen Sprache als SQL geschrieben zum Beispiel in Java oder in C. Wir werden diese
Möglichkeit in diesem Kapitel nich weiter verfolgen.
8-1
8.1.1
SQL-Routinen
Die Deklaration und Implementierung einer SQL-Routine erfolgen gemeinsam in einer Definition. Die Signatur einer SQL-Routine darf nur SQL-Datentypen enthalten. Der Rumpf der
Routine besteht aus einer SQL-Anweisung, die eine Zusammengesetzte Anweisung sein darf.
Achtung:
In der Implementation sind nich alle SQL-Anweisungen erlaubt. Ausgeschlossen sind unter
anderem Datendefinitionsanweisungen, Transaktionsanweisungen (COMMIT und ROLLBACK)
sowie Datenverbindungsanweisungen (CONNECT und DISCONNECT).
Die Syntax für die Definition von Prozeduren und Funktionen ist nachstehend angegeben.
<procedure>
::=
CREATE PROCEDURE
<prozedur-name> ([<Parameterliste>])
[<Routinencharakteristik>]...
<Routine-Body>
<function>
::=
CREATE FUNCTION
<function-name> ([<Parameterliste>])
{<data-type> | <table-type>}
[<Routinencharakteristika>]...
<SQLStatement>
RETURNS
<Parameterliste>
::= <Parameterdef > [,<Parameterdef >]...
<Parameterdef >
::=
[IN | OUT | INOUT]
<table-type>
::=
TABLE(
<parameter-name> <data-type>
<Column-Element> [,<Column-Element>]...)
<Column-Element> ::= <column-name> <data-type>
Die Schlüsselworte IN, OUT und INOUT stehen für Eingabeparameter, Ausgabeparameter
bzw. kombinierte Ein- und Ausgabeparameter. Falls nichts angegeben ist, so wird implizit IN
angenommen. Die bedeutung der Routinencharakteristika werden wir später anschauen.
Im Beispiel 8.1 ist eine SQL-Prozedur angegeben, die das Ergebnis einer Berechnung über
einen Parameter zurückgibt.
Beispiel 8.1 [Out Parameter]
CREATE PROCEDURE
bestellt (IN Pnr INTEGER,
OUT Menge INTEGER)
BEGIN
SELECT SUM(bp.menge) INTO
Menge -- Setzen OUT
bestellung b NATURAL JOIN b_p bp
WHERE b.lieferdatum IS NULL AND
bp.p_nr = Pnr;
FROM
END
8-2
Im Gegensatz zu Prozeduren kann bei Funktionen für die Parameter IN, OUT, INOUT nicht
angegeben werden. Alle Parameter sind implizit IN. Jede Funktion besitzt einen Rückgabewert. Der Datentyp wird durch die RETURNS-Klausel festgelegt. Für die Asführung einer
Funktion gilt, dass sie nur mit einer RETURN-Anweisung (siehe 8.1.2) beendet werden darf.
Ist dies nicht der Fall, so gibt es zur Laufzeit eine Exception.
Beispiel 8.2 [Return Parameter]
CREATE FUNCTION
fbestellt (Pnr
INTEGER)
RETURNS INTEGER
SQL SECURITY INVOKER
BEGIN
DECLARE
Su
INTEGER;
SELECT SUM(bp.menge) INTO
Su
bestellung b NATURAL JOIN b_p bp
b.lieferdatum is NULL AND
bp.p_nr = Pnr);
RETURN Su;
FROM
WHERE
END
8.1.1.1
Routinencharakteristika
Die Deklaration einer Routine kann eine Charakteristikaklausel enthalten, die die Merkmale
der Routine explizit beschreibt. In der folgenden Liste sind die für Routinen wichtigsten
Charakteristika angegeben.
Die Sprachklausel gibt an, mit welcher Programmiersprache die Routine implementiert
wurde:
LANGUAGE
{ADA |
C
|
COBOL
|
FORTRAN
|
JAVA
|
MUMPS
|
PASCAL
|
PLI
|
SQL}
Die Determinismusklausel drückt explizit aus, ob die Routine nichtdeterministische Anweisungen wie zum Beispiel CURRENT_TIME enthält oder nicht.
[NOT DETERMINISTIC |
DETERMINISTIC]
Aufrufe von nicht deterministischen Routinen dürfen nicht Teil von Check-Klauseln oder
Case-Ausdrücken sein.
Die Zugriffsklausel:
NO SQL:
Die Routine enthält kein SQL. Nur bei externen Routinen erlaubt.
CONTAINS SQL:
Die Routine enthält keine lese-/schreibe-Anweisungen.
READS SQL DATA:
Die Routine liest eventuell SQL-Daten.
MODIFIES SQL DATA:
Die Routine liest und verändert eventuell SQL-Daten.
Die Nullaufrufsklausel bestimmt, ob die Routine aufrufbar ist, wenn einer der Parameter
NULL ist.
RETURNS NULL ON NULL INPUT:
NULL,
Ist beim Aufruf der Routine einer der Parameter
so wird die Routine nicht ausgeführt und der Wert NULL zurückgeliefert.
8-3
CALLED ON NULL INPUT:
Die Routine wird in jedem Fall aufgerufen.
Achtung: Eine Prozedurdeklaration darf keine Nullaufrufsklausel enthalten.
Die Security Klausel SQL SECURITY INVOKER Die Routine wird mit den Privilegien des
Aufrufers abgearbeitet.
Die Routine wird mit den Privilegien des Erstellers der Routine abgearbeitet. Die Funktion CURRENT_USER gibt dann innerhalb der Routine
den Namen des Erzeugers zurück.
SQL SECURITY DEFINER
Die Klausel SPECIFIC <Name> Mit dieser Klausel kann einer Funktion die überladen
ist ein eindeutiger Name gegeben werden. Dieser Name kann dann in DROP, ALTER,
GRANT oder REVOKE Statements verwendet werden.
Es gibt noch weitere Routinencharakteristika, die aber nur auf externen Routinen anwendbar
sind. Voreingestellt ist:
LANGUAGE SQL, NOT DETERMINISTIC, CONTAINS SQL, CALLED ON NULL INPUT, SQL SECURITY DEFINER
Das heisst, für normale SQL-Routinen kann meistens die Default-Einstellung gewählt werden.
8.1.1.2
Tabellen als Rückgabewert
Ab SQL:2003 können SQL-Funktionen Tabellentypen zurückliefern. Eine solche Tabellenfunction kann dann im Rahmen einer Anfrage als Tabellenreferenz verwendet werden.
Beispiel 8.3 [Tabelle als Returnwert] Die folgende Funktion gibt eine Tabelle von Tupeln
zurück. Pro Lieferant die Anzahl Bestellungen.
CREATE FUNCTION
RETURNS TABLE
Nr
AnzahlBestellung()
INTEGER, Name VARCHAR(30), Anzahl
INTEGER
LANGUAGE SQL
RETURN
( SELECT l.l_nr, l.name, COUNT(*)
FROM lieferant l natural join besetllung b
GROUP BY l.l_nr, l.name);
Die gerade definierte Funktion kann nun als Tabellenreferenz verwendet werden.
SELECT
FROM
WHERE
8.1.2
ab.Name, ab.Anzahl
AnzahlBestellung() ab
ab.Anzahl > 2
Prozedurale Erweiterung von SQL
In diesem Abschnitt wollen wir die prozeduralen Erweiterung von SQL anschauen. Die folgenden Anweisungen dürfen nur in SQL-Routinen verwendet werden.
8-4
8.1.2.1
Zusammengesetzte SQL-Statement
Das Zusammengesetzte Statement (oder Blockstatement) fasst mehrere Anweisungen zu einer
Einheit zusammen.
<stmtblock> ::=
[[NOT] ATOMIC]
<SQLStatement>; [<SQLStatement>;]...
BEGIN
END
Mit der Angabe ATOMIC wird eine Atomare Ausführung der Einheit gewährleistet.
ATOMIC ist der Default.
Im folgenden bezeichnet
8.1.2.2
SQLStatement
NOT
eine einzelne oder zusammengesetzte SQL-Anweisung.
Deklaration einer Variablen
Lokale Variablen müssen wie in einer Programmiersprache deklariert und initialisiert werden.
<declaration> ::= DECLARE <variablen-name> <daten-typ> [DEFAULT <literal>]
8.1.2.3
Wertzuweisung an eine Variable
<assignment> ::=
8.1.2.4
RETURN
<expression>
Aufruf einer Prozedur
<callstmt> ::=
8.1.2.6
<variablen-name> = <expression>
Verlassen einer Routine mit einem Rückgabewert
<returnstmt> :: =
8.1.2.5
SET
CALL
<prozedur-name>([<parameterliste>])
Bedingte Ausführungen
Dazu stehen die zwei folgenden Kontrollflusskonstrukte zur Verfügung:
<ifstmt> ::=
IF <conditional-expression> THEN <SQLStatement>
[ELSEIF <conditional-expression> THEN SQLStatement]...
[ELSE <SQLStatement>]
END IF
Beispiel 8.4 [Auswahl] Das Folgende Beispiel benutzt die IF-Anweisung um verschiedene
Selects auszuführen.
8-5
CREATE PROCEDURE
bsp4(Test
INTEGER)
BEGIN
IF
Test = 0
THEN
p_nr, name FROM person;
ELSEIF Test = 1 THEN
SELECT l_nr, name FROM lieferant;
SELECT
ELSE
SELECT
p_nr, bezeichnung
FROM
produkt;
END IF;
END
Die folgende Case-Anweisung unterscheidet sich von der obigen If-Anweisung darin, dass sie
eine Exception auslöst, wenn keines der When-Bedingungen erfüllt ist und keine Else-Klausel
angegeben wurde.
<casestmt> ::=
CASE
<conditional-expression> THEN <SQLStatement>
[WHEN <conditional-expression> THEN <SQLStatement>]
[ELSE <SQLStatement>]
WHEN
END CASE
Die folgende Case-Anweisung führt einen WHEN-Zweig aus, wenn der Wert der zugehörigen
<expression> gleich dem Wert der <expression> hinter dem CASE ist.
<casestmt> ::=
<expression>
<expression> THEN <SQLStatement>
[WHEN <expression> THEN <SQLStatement>]
[ELSE <SQLStatement>]
CASE
WHEN
END CASE
Beispiel 8.5 [Case Anweisung] Das Folgende Beispiel benutzt die Case-Anweisung um
verschiedene Selects auszuführen. Falls die Variable Test nicht 0 oder 1 ist, so wird eine
Exception ausgelöst.
CREATE PROCEDURE
bsp5(Test
INTEGER)
BEGIN
CASE
Test
WHEN
WHEN
0
1
THEN SELECT
THEN SELECT
p_nr, name FROM person;
l_nr, name FROM lieferant;
END CASE;
END
8-6
8.1.2.7
Schleifen
Es gibt vier arten von Schleifen. Eine Schleife kann explizit mit einem Label versehen werden.
Das Label kann zum Beenden einer Schleife verwendet werden.
Loop Schleife:
<loopstmt> ::= [<label>:]
LOOP
<SQLStatement>
END LOOP [<label>]
While Schleife:
<whilestmt> ::= [<label>:]
WHILE <conditional-expression>
<SQLStatement>
END WHILE [<label>]
DO
Repeat Schleife:
<repeatstmt> ::= [<label>:]
REPEAT
<SQLStatement>
UNTIL <conditional-expression>
END REPEAT [<label>]
Eine For-Schleife iteriert über alle Elemente der Ergebnismenge einer Anfrage bzw. eines
Cursors.
<forstmt> ::= [<label>:]
FOR <loopvariable> AS
[<cursor-name> CURSOR FOR]
<select-befehl> DO
<SQLStatement>
END FOR [<label>]
Die Funktion im folgenden Beispiel zählt die Lagermenge aller Produkte zusammen.
Beispiel 8.6 [Loops]
CREATE FUNCTION
TestCurs()
RETURNS INTEGER
BEGIN
DECLARE
p
INTEGER DEFAULT
FOR
prod
curs
CURSOR FOR
SELECT
0;
AS
lagermenge
FROM
produkt
8-7
DO
SET
p = p + lagermenge;
END FOR;
END
8.1.2.8
Verlassen von Schleifen:
Mit Hilfe der Label kann eine Iteration einer Schleife abgebrochen werden oder die Schleife
ganz verlassen werden.
<iteratestmt> ::=
ITERATE
<label-name>
Diese Anweisung beendet die aktuelle Iteration der mit label-name markierten Schleife.
<leavestmt> ::=
LEAVE
<label-name>
Diese Anweisung beendet die mit label-name markierte Schleife.
8.1.2.9
Fehlerbehandlung
Die folgende Anweisung dient dazu, einem Fehlercode (SQLSTATE) einen Namen zu geben, der
nachfolgend bei der Definition von Fehlerhandler und beim signalisieren von Fehler verwendet
werden kann.
<condiditiondeclare> ::=
DECLARE
<condition-name>
FOR SQLSTATE
<sqlstate-value>
Im Standard sind die folgenden Namen für <sqlsate-value> schon vordefiniert:
SQLWARNING:
Dieser Name steht für alle Warnings, die von SQL generiert werden. (SQLSTATE
= ’01xxx’).
NO DATA:
Diese Bedingungen werden generiert, wenn einer der SQL-Befehle
keine Daten gefunden hat (SQLSTATE = ’02xxx’).
SELECT
oder
SQLWARNING
noch
FETCH)
SQLEXCEPTION:
Steht für alle Werte von
durch NO DATA abgedeckt sind.
SQLSTATE
die weder durch
In einer Blockanweisung kann ein Handler deklariert werden. Wenn im Block eine Exception
passiert, so wird der Handler aufgerufen.
Declaration des Handlers
{CONTINUE| EXIT|UNDO}
<condition-value> [, <condition-value>]...
<SQLStatement>
<condition-value>::= {<sqlstate-decl>|<condition-name>
|SQL EXCEPTION|SQL WARNING|NOT FOUND}
<handler>
::=
DECLARE
HANDLER FOR
<sqlstate-decl>
::=
SQLSTATE
<sqlstate-value>
8-8
Handler unterscheiden sich in der Art, wie sie nach ihrem Aufruf die Kontrolle an das Programm zurückgeben:
CONTINUE: Das Programm fährt mit der nächsten Anweisung fort.
EXIT: Das Programm verlässt den aktuellen Block und fährt mit dem nächsten Block fort.
UNDO: Wie EXIT, nur dass alle Änderungen des Blocks zurückgesetzt werden.
Eine Exception kann durch eine SQL-Anweisung ausgelöst werden, was der Normalfall sein
dürfte. Man kann aber auch mit der SIGNAL-Anweisung direkt eine Exception auslösen
<signalstmt> ::=
SIGNAL
{<exception-name>|<sqlstate-decl>} [SET <informationsliste>]
Die Informationsliste kann beispielsweise eine Zuweisung der Form MESSAGE.TEXT = <information>
enthalten.
Im nächsten Beispiel ist die Prozedur aus den Beispiel 8.6 umgeschrieben. Der For-Loop wird
hier mit Hilfe eines While-Loops und eines Handlers simmuliert.
Beispiel 8.7 [Error Handler]
CREATE FUNCTION
RETURNS
BEGIN
testcurs()
INTEGER
DECLARE
DECLARE
DECLARE
lg INTEGER DEFAULT 0;
p INTEGER DEFAULT 0;
done BOOLEAN DEFAULT false;
curs CURSOR FOR
lagermenge FROM produkt;
DECLARE
SELECT
BEGIN
– Handler fuer diesen Block
DECLARE CONTINUE HANDLER
FOR NOT FOUND
SET
done = true;
curs;
FETCH curs
OPEN
lg;
done DO
SET p = p + lg;
FETCH curs INTO lg;
END WHILE;
CLOSE curs;
INTO
WHILE NOT
END
END
8-9
8.2
Trigger
Trigger definieren automatische Reaktionen auf Ereignisse, die durch Datenmanipulationen
auf einer Tabelle ausgelöst werden.
Bemerkung 8.4 [Aktive Datenbanken] Man könnte sich auch vorstellen, dass Triggers
auch auf externe Benutzerdefinierte Ereignisse reagieren könnten. In diesem Fall spricht man
dann ganz allgemein von einem aktiven Datenbanksystem. Wir wollen hier aber nicht näher
auf aktive Datenbanksysteme eingehen.
Die Definition eines Triggers geschieht mit der folgenden Syntax:
createtrigger ::=
<trigger-name>
{BEFORE|AFTER}
{INSERT|DELETE|UPDATE [OF <attributliste>]}
ON <table-name>
[REFERENCING {<transitionsvariablen>|<transitionstabellen>}]
[FOR EACH {ROW|STATEMENT}]
[WHEN (<bedindung>)]
<SQLStatement>
CREATE TRIGGER
Nun die Erklärungen zu den einzelnen Klauseln:
Aktivierungszeitpunkt: Die Aktivierung des Triggers erfolgt direkt vor (BEFORE) oder nach
(AFTER) der effektiven Abarbeitung des Aktivierungsereignisses. Before-Trigger dürfen
weder SQL-Anweisungen noch Routinen verwenden, welche Daten in der Datenbank
verändern. Das setzen der Transitionsvariablen NEW mit Hilfe der SET-Anweisung ist jedoch erlaubt. Mit diesem Mechanismus ist es möglich vor einem INSERT oder UPDATE
noch eine Vorverarbeitung der Daten vorzunehmen.
Aktivierungsereignis: Ein Trigger kann durch eine auf eine Basistabelle ausgeführten Datenmanipulationsanweisung INSERT, UPDATE oder DELETE aktiviert werden.
Transitionsvariablen und tabellen: ermöglichen den Zugriff auf die von der Anweisung
(UPDATE, INSERT, DELETE) betroffenen Zeilen. Transitionsvariablen können in der
Bedingung oder im Rumpf von Triggern verwendet werden.
Syntax:
transitionsvariablen ::=
OLD [ROW] [AS]
<variablenname>
<variablenname>
transitionstabellen ::= OLD TABLE [AS] <variablenname>
NEW TABLE [AS] <variablenname>
NEW [ROW] [AS]
OLD und NEW bestimmen den Zustand der Zeilen vor bzw. nach der Ausführung der
Anweisung. OLD darf nicht in Insert-Triggern definiert werden. NEW ist dagegen für
Delete-Trigger nicht erlaubt.
Granularität: Entweder wird der Trigger für alle Einzeländerungen aufgerufen (FOR EACH
ROW) oder einmal pro Anweisung (FOR EACH STATEMENT). In Anweisungstrigger können
keine Transitionsvariablen verwendet werden sondern nur Transitionstabellen.
8-10
Bedingung: Die Ausführung der Triggeraktion kann durch die Angabe der WHEN-Klausel an
eine Bedingung gekoppelt werden.
Aktion: Der Triggerrumpf besteht aus einer einzelnen oder zusammengesetzten SQL-Anweisung die atomar sein muss.
Bemerkung 8.5 [Aktivierungsreihenfolge] Jeder Trigger besitzt intern einen Zeitstempel, der den Zeitpunkt der Erzeugung des Triggers angibt. Die Zeitstempel legen die Aktivierungsreihenfolge fest, wenn mehrere Trigger von einem Aktivierungsereignis betroffen
sind. Es werden zuerst die Trigger mit dem ältesten Zeitstempel aktiviert.
Trigger eignen sich gut für die Umsetzung sogenannter transitionaler Integritätsbedingungen.
Das heisst, Bedingungen, die sowohl auf die alten wie auch auf die neuen Werte eines Tupels zugreifen. Man kann auch sagen, dass transitionale Integritätsbedingungen semantisch
inkorrekte Datenbankzustandstransitionen ausschliessen.
Im nächsten Beispiel 8.8 wird verhindert, dass eine gelieferte Bestellung wieder auf “nicht
geliefert” zurückgestellt wird. Dies ist dann der Fall, wenn das alte Lieferdatum gesetzt ist
und der neue Wert null ist.
Beispiel 8.8 [Transitionale Integritätsbedingung]
bestellTrigger
bestellung
OLD AS alt NEW AS neu
CREATE TRIGGER
AFTER UPDATE ON
REFERENCING
FOR EACH ROW
BEGIN ATOMIC
DECLARE
IF
myexception
EXCEPTION FOR SQLSTATE
’99001’;
(alt.lieferdatum IS NOT NULL AND
neu.lieferdatum IS NULL)
THEN
SIGNAL
myexception;
END IF;
END
Der Trigger im Beispiel 8.9 wird benutzt, um die Lagermenge im Produkt aktuell zu halten.
Er wird nur ausgelöst, wenn das Lieferdatum in der Bestellung verändert wird. Falls das alte
Lieferdatum null ist und das neue als nicht null gesetzt wird, so wird die Lagermenge aller in
dieser Bestellung vorkommenden Produkte entsprechend nachgeführt.
Beispiel 8.9 [Updates mit Triggers]
bestellTrigger1
AFTER UPDATE OF lieferdatum ON bestellung
REFERENCING OLD AS alt NEW AS neu
CREATE TRIGGER
FOR EACH ROW
(alt.lieferdatum IS NULL AND
neu.lieferdatum IS NOT NULL)
WHEN
BEGIN ATOMIC
DECLARE
DECLARE
p INTEGER;
m INTEGER;
8-11
done INTEGER DEFAULT 0;
DECLARE mycursor CURSOR FOR
SELECT bp.p_nr, (bp.menge * p.liefereinheit)
FROM b_p bp NATURAL JOIN produkt p
WHERE bp.b_nr = alt.b_nr;
DECLARE
AS
anz
DECLARE CONTINUE HANDLER FOR NOT FOUND SET
done = 1;
mycursor;
mycursor into p, m;
WHILE (done = 0) DO
UPDATE produkt SET lagermenge = lagermenge + m
WHERE p_nr = p;
FETCH mycursor into p, m;
END WHILE;
OPEN
FETCH
END
8-12
8.3
Übungen
Aufgabe 8.1 [Erfassungshilfe] Schreiben Sie zwei Prozeduren zum Erfassen von neuen
Bestellungen in der Datenbank. Mit der ersten Prozedr sollen der Bestellkopf, (Tabelle bestellung)
sowie der erste Bestellposten (Tabelle b_p) eingefügt werden. Mit der zweiten Prozedur sollen
weitere Posten eingefügt werden.
Aufgabe 8.2 [Materialisierte View] Es geht darum eine materialisierte View mit Hilfe von
Triggern nachzuführen. Die Nachfolgend definierte View prodview soll mit Hilfe von Triggern
und Prozeduren immer aktuell gehalten werden.
Die folgenden SQL-Definition kreiert die Materialisierte View.
– Kreieren der materialisierten VIEW prodview
prodview (
p_nr INTEGER PRIMARY KEY,
bezeichnung VARCHAR(30),
jahrgang INTEGER,
stueckzahl INTEGER
CREATE TABLE
)
– Daten in die materialisierte VIEW eintragen
prodview
p.p_nr, p.bezeichnung, p.jahrgang,
COALESCE(SUM(menge * liefereinheit), 0) stueckzahl
(SELECT * FROM bestellung NATURAL JOIN b_p WHERE lieferdatum is null) b
NATURAL RIGHT JOIN produkt p
p.p_nr, p.bezeichnung, p.jahrgang)
INSERT INTO
(SELECT
FROM
GROUP BY
8-13
Kapitel 9
SQL in Programmiersprachen
Bis jetzt haben wir eine Datenbank über den Interpreter “dbframe” benutzt und jeden Befehl
für eine Anfrage oder Änderung dort eingegeben. Wir haben also ausschliesslich die deklarative
Seite von SQL ausgenutzt. Für grössere Applikationen, die mit einer Datenbank arbeiten,
benötigen wir häufig die Zusammenarbeit mit einer Sprache wie C, C++, Java, C# usw.
Diese Koppelung wird als Wirtssprachen-Koppelung bezeichnet.
Bemerkung 9.1 [Deklarative Sprache] Mit der reinen deklarativen Sprache SQL ist es
nicht möglich, rekursive Strukturen (z.B. Stücklisten) zu verarbeiten. In solchen Fällen ist
eine volle Programmiersprache wie C++ oder Java notwendig.
Damit SQL-Befehle in einer Wirtssprache sinnvoll genutzt werden können, müssen folgende
Bedingungen erfüllt sein:
• Die Schutzmechanismen der Datenbank müssen auch für Programme, die in einer 3GLSprache geschrieben sind, eingehalten werden. Dies bezieht sich sowohl auf die Zugriffsrechte, wie auch auf in der Datenbank definierte Constraints (Fremdschlüssel, Assertions
usw).
• Innerhalb der SQL-Befehle müssen Parameter (Variablen) der Wirtssprache verwendet
werden können. Es muss klar definiert werden, wie die Datentypen der Datenbank in
Datentypen der Wirtssprache übersetzt werden.
• Der Wirtssprache muss Kontroll-Information zur Verfügung gestellt werden, damit sie
das Resultat einer Abfrage auswerten (z.B. Anzahl Tupel, die ein “select”-Befehl ergeben
hat) oder Fehlersituationen erkennen kann (z.B. keine “insert”-Berechtigung auf einer
Tabelle).
Im folgenden wollen wir JDBC™ mit (Java) und embedded SQL (mit C) als mögliche Datenbankanbindungen betrachten.
9.1
Java Datenbankanbindung JDBC™
Der folgende Abschnitt ist nur eine kurze Einführung und erhebt keinen Anspruch auf vollständigkeit. Für weitere Informationen sei auf das Buch [HCF97] der Java Serie verwiesen.
Weiter existiert im Internet unter
http://download.oracle.com/javase/6/docs/api/index.html
9-1
eine online Dokumentation der jdbc API Klassen und Interfaces in den Packages java.sql und
javax.sql.
9.1.1
Was ist JDBC™
Nachfolgend einige Informationen über JDBC™.
• JDBC = Java DataBase Connectivity ist mit ODBC (Open Database Connectivity)
von Microsoft vergleichbar.
• JDBC ist ein low-level oder call-level API um
– Die Verbindung zu einer Datenbank herzustellen
– SQL-Befehle abzusetzen
– Resultate zu analysieren und zu verarbeiten
– Information über die Datenbank bereitzustellen.
• JDBC ermöglicht einen produktunabhängigen Datenbankzugriff, erzwingt ihn aber nicht.
Das heisst, mit JDBC können auch proprietäre SQL-Befehle ausgeführt werden.
• JDBC ist eine Basis für high-level API’s, die das Arbeiten mit SQL vollständig verstecken und z.B. direkt Java-Objekte verwalten.
9.1.2
9.1.2.1
Aufbau von JDBC™
Package-Struktur
In der Abbildung 9-1 ist die Package-Struktur von JDBC™angegeben.
<<implements>>
<<uses>>
UserPackage
java.sql
VendorPackage
Abbildung 9-1: Package-Struktur von JDBC™
9.1.2.2
Erklärungen:
java.sql: Dieses Package gehört ab Java 1.1 zum Standard API.
VendorPackage: Dieses Package enthält für jedes Interface von java.sql eine entsprechende
Implementationsklasse. Es stellt die eigentliche Funktionalität zur Verfügung und wird
von Datenbank- oder Drittherstellern vertrieben.
UserPackage: Dieses Package erzeugt und benützt Variablen der verschiedenen Interfaces
von java.sql. Es ist ausserdem verantwortlich für das erstmalige Laden eines Drivers aus
dem VendorPackage, je nach gewünschter Kommunikationsart und Datenbankprodukt.
9-2
9.1.2.3
Das Package java.sql
In der Abbildung 9-2 sind die wichtigsten Interfaces und Klassen des Packages java.sql
dargestellt.
<<interface>>
ResultSet
<<interface>>
Statement
<<creates>>
<<class>>
<<interface>>
DriverManager
Driver
<<creates>>
<<creates>>
<<creates>>
<<interface>>
<<interface>>
ResultSetMetaData
PreparedStatement
<<interface>>
<<interface>>
Connection
<<creates>>
<<creates>>
DatabaseMetaData
<<interface>>
CallableStatement
<<creates>>
Abbildung 9-2: Das Package java.sql
Bemerkung 9.2 [Namen von Implementationsklassen] Damit der Java-Code unabhängig von der verwendeten Datenbank ist gelten für die Erzeugung von Objekten die folgenden Regeln:
• Objekte von Implementationsklassen im VendorPackage werden niemals direkt mit new
erzeugt, sondern jeweils mit einer Get- oder Create-Methode in einem anderen Interface.
• Die Namen der Implementationsklassen bleiben dem Anwender Package verborgen, mit
Ausnahme der Implementationsklasse des datenbankspezifischen Drivers. Die verwendeten Drivers müssen vom Anwender Package geladen werden.
Nun noch die Erklärungen zu den Interfaces und Klassen in der Abbildung 9-1.
Driver: Die Aufgabe des Drivers ist, eine Verbindung zwischen dem Java-Programm und der
Datenbank herzustellen. Eine Driverimplementation muss eine “static section” enthalten, die beim Laden des Drivers die folgenden Aufgaben übernimmt:
1. Eine Instanz von sich selbst kreieren und
2. diese Instanz mit der Methode DriverManager.registerDriver beim Drivermanager zu registrieren.
Somit ist es möglich mehrere verschiedene Drivers zu laden und gleichzeitig mit verschiedenen Datenbanksystemen zu Kommunizieren.
Die gewünschten Drivers können mit der Methode Class.forname geladen werden. Der
Befehl
Class.forname("com.mysql.jdbc.Driver")
wird den entsprechenden Mysql-Driver laden und registrieren.
9-3
DriverManager: Diese Klasse enthält nur statische Methoden. Der Konstruktor ist privat,
so dass keine Instanzen dieser Klasse erzeugt werden können. Für den Anwender ist
nur die Methode getConnection, welche die Verbindung mit der Datenbank aufnimmt
wichtig. Der Methode muss eine sogennante JDBC™URL übergeben werden. An Hand
dieser URL und der registrierten Drivers weiss der Drivermanager welche Datenbank
geöffnet werden muss. Die JDBC™URL hat das folgende Format:
jdbc:<subprotocol>:<subname>
<subprotocol> – ist der Name eines Drivers oder eines Connectivity Mechanismus, der
von mehreren Drivern implementiert werden kann (z.B. odbc). Subprotokolle können
von Driver Herstellern bei Javasoft registriert werden.
<subname> – dient dazu, die Datenbank zu identifizieren. Der genaue Aufbau dieses
Teiles ist Herstellerabhängig.
Der Befehl:
Connection con = DriverManager.getConnection(
"jdbc:mysql://db.bfh.ch/fierzdb"
"Name", "Passwort")
nimmt die Verbindung mit der Datenbank auf und kreiert ein Object des Typs Connection. In diesem Beispiel ist mysql der Name des Subprotokolls und
//pinguin.bfh.ch/fierzdb der Subname.
Connection, Statement, ResultSet: Mit Hilfe einer Connection können Statementobjekte (Statement, PreparedStatement und CallableStatement) erzeugt werden, die dazu
dienen, SQL-Befehle an die Datenbank zu schicken. Im Interface Statement sind drei
Methoden besonders wichtig:
1. executeQuery: für SELECT
2. executeUpdate: für INSERT, UPDATE, DELETE, CREATE, DROP usw.
3. execute: für SQL-Befehle, die mehr als ein Resultat liefern (stored Procedures);
Falls ein SQL-Befehl eine Tabelle zurückliefert, ist diese in einem Objekt des Typs
ResultSet gespeichert. Die Methode next iteriert durch alle Zeilen hindurch. Mit den
Methoden getxxx() können die Datenfelder in entsprechende Java-Objekte umgewandelt werden.
Die Klasse CallableStatement dient dazu, den Aufruf von “stored Procedures” für alle
Datenbanken zu vereinheitlichen. Dabei können die Input- und Output-Parameter und
der Rückgabewert in einheitlicher Form definiert werden.
DatabaseMetadata: Mit diesem Interface können Informationen über das Schema einer
Datenbank gewonnen werden (Relationsschematas, Attributte, Primärschlüssel usw.).
ResultSetMetaData: Mit diesem Interface können Informationen über ein Objekt des Typs
ResultSet gewonnen werden. Dies wird benutzt, wenn das Resultat eines Queries zur
Kompilationszeit nicht bekannt ist.
9-4
Neben den Klassen und Interfaces in der Abbildung 9-1 existieren noch die folgenden Klassen
im Package java.sql:
Exceptions JDBC™kennt drei arten von Exceptions:
• SQLExceptions
Diese werden ausgeworfen, wenn ein Datenbankbefehl nicht durchgeführt werden
kann, weil z.B. eine Tabelle nicht vorhanden ist, die nötigen privilegien nicht
vorhanden sind, die Syntax einer Abfrage falsch ist usw.
• SQLWarnings
werden erzeugt, wenn ein Datenbankbefehl zwar durchgeführt wird, aber ungewöhnliche Resultate liefert, z.B. ein update-Befehl, der keine Einträge verändert. Warnings werden nicht ausgeworfen, sondern an das Objekt angehängt, das die verursachende Methode enthält. Warnings können mit der Methode getWarnings()
abgeholt werden.
• DataTruncation
Falls Daten beim Schreiben in die Datenbank abgeschnitten werden müssen, wirft
der JDBC-Driver eine DataTruncation Exception. Falls das Abschneiden beim
Lesen passiert wird die Exception nicht ausgeworfen sondern nur am entsprechenden Objekt angehängt.
Date, Time, TimeStamp: Diese Klassen garantieren die Kompatibilität zwischen Datenbank und Java bezüglich Zeit und Datum.
DriverPropertyInfo: Diese Klasse dient dazu, Informationen über einen bestimmten Driver
zu speichern. Die Informationen können mit der Methode getPropertyInfo des Drivers
geholt werden. Zum Beispiel kann man so erfahren, welche Parameter beim Öffnen der
Datenbank (connect) notwendig sind (user, password usw.).
Types: Diese Klasse enthält Konstanten, welche die generischen SQL-Typen definieren. In
der Nachfolgenden Tabelle ist angegeben, wie die generischen SQL-Datentypen in JavaTypen umgewandelt werden können. Ein X bedeutet, dass dies die von Java bevorzugte
Konversion ist. Ein x bedeutet, dass die Konversion möglich ist.
9-5
T
S
I
B
R
F
D
D
N
B
C
V
L
B
V
L
D
T
T
I
M
N
I
E
L
O
E
U
I
H
A
O
I
A
O
A
I
I
N
A
T
G
A
O
U
C
M
T
A
R
N
N
R
N
T
M
M
Y
L
E
I
L
A
B
I
E
R
C
G
A
B
G
E
E
E
I
L
G
N
T
L
M
R
H
V
R
I
V
T
E
A
I
A
A
Y
N
A
T
L
C
R
R
A
R
A
C
R
B
M
H
Y
I
P
N
I
E
T
N
R
T
A
N
R
A
S
R
Y
getByte
X
x
x
x
x
x
x
x
x
x
x
x
x
getShort
x
X
x
x
x
x
x
x
x
x
x
x
x
getInt
x
x
X
x
x
x
x
x
x
x
x
x
x
getLong
x
x
x
X
x
x
x
x
x
x
x
x
x
getFloat
x
x
x
x
X
x
x
x
x
x
x
x
x
getDouble
x
x
x
x
x
X
X
x
x
x
x
x
x
getBigDecimal
x
x
x
x
x
x
x
X
X
x
x
x
x
getBoolean
x
x
x
x
x
x
x
x
x
X
x
x
x
getString
x
x
x
x
x
x
x
x
x
x
X
X
x
getDate
x
x
x
getTime
x
x
x
getTimestamp
x
x
x
getAsciiStream
x
x
X
x
x
x
getUnicodeStream
x
x
X
x
x
x
x
x
X
x
x
x
x
x
x
getBytes
getBinaryStream
getObject
9.1.3
x
x
x
x
x
x
x
x
x
x
x
x
x
X
X
x
x
x
X
x
x
X
x
x
x
X
x
x
x
Programmieren mit JDBC™
In diesem Abschnitt wollen wir einige Anwendungen von JDBC™betrachten. Für weitere
Informationen sei noch einmal auf das Buch [HCF97] der Java Serie verwiesen.
9.1.3.1
Verbindungsaufbau und einfache Selects
Das folgende Programm zeigt, wie die Verbindung zur Datenbank aufgebaut wird und wie
ein einfacher Select durchgeführt werden kann. Der Query wird mit Hilfe eines Statementobjekts abgesetzt. Anschliessend können die einzelenen Tuples mit Hilfe des Zurückgegebenen
Resultsetobjektes nacheinander eingelesen werden.
Beispiel 9.1 [Connection Aufbau]
package tests;
// Importieren des JDBC-Package java.sql
import java.sql.Connection;
9-6
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcExamples {
public static void main(String args[]) {
Connection con = null;
if (args.length < 2) {
System.out.println("User und Passwort angeben");
System.exit(0);
}
String user = args[0];
String passwd = args[1];
// Laden des Mysql-Treibers. Es ist auch
// moeglich mehrere Treiber zu laden.
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {
// Fehler ausgeben und Programm verlassen
System.out.println(e);
System.exit(1);
}
// Verbindung mit der Datenbank aufnehmen. Mit Hilfe
// des Subprotokolls "mysql" wird der
// Drivermanager wissen, dass die Verbindung zu einer
// Mysql-Datenbank aufgenommen werden muss.
//
// Bei Mysql muessen noch User-Name und Passwort
// mitgegeben werden.
try {
con = DriverManager.getConnection(
"jdbc:mysql://pinguin.bfh.ch/fierzdb",
user, passwd );
} catch (Exception e) {
// Verbindung kann nicht aufgenommen werden.
System.out.println(e);
System.exit(1);
}
// Beispiel SELECT Befehl
try {
Statement stmt = con.createStatement();
// Aufbauen eines Queries
ResultSet rs = stmt.executeQuery(
"SELECT l_nr, name, ort, plz " +
"FROM lieferant");
while (rs.next()) {
System.out.print(rs.getInt(1) + " / " +
9-7
rs.getString(2) + " / ");
// Es ist auch moeglich mit dem Namen des Feldes
// auf die Daten zuzugreifen.
System.out.println(rs.getString("plz") + " " +
rs.getString("ort"));
}
// Statement explizit schliessen, damit die
// Datenbankresourcen sofort freigegeben werden und
// nicht erst bei einer Garbagecollection.
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
9.1.3.2
Update-Operationen
Das folgende Programmfragment zeigt, wie Tuple in einer Tabelle eingefügt werden können
und auch wie das Datenbankschema verändert werden kann.
Beispiel 9.2 [Update operationen]
...
// UPDATE Befehle
try {
Statement stmt = con.createStatement();
// Absetzen eines Inserts. Dazu muss die Methode
// executeUpdate verwendet werden.
int changes = stmt.executeUpdate(
"INSERT INTO lieferant (l_nr, name, ort, plz)" +
"VALUES (102, ’fierz’, ’Bern’, ’3006’)");
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
// Kreieren einer neuen Tabelle
try {
Statement stmt = con.createStatement();
stmt.executeUpdate("CREATE TABLE kunde (" +
"kd_nr INTEGER NOT NULL, " +
"name VARCHAR(30) NOT NULL, " +
"ort
VARCHAR(30) NOT NULL," +
"PRIMARY KEY (kd_nr))");
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
...
9-8
9.1.3.3
Prepared Statement
Prepared Statements dienen dazu, oft verwendete Befehle zu beschleunigen und gleichzeitig
die Parameterübergabe zu vereinfachen. Prepared Statement sind für SELECT, UPDATE,
INSERT und DELETE Befehle anwendbar.
Beispiel 9.3 [Prepared Statement]
...
// Prepared Statements mit Parameteruebergabe.
// erlaubt es ein SQL-Befehl mehrmals zu verwenden.
try {
PreparedStatement stmt = con.prepareStatement(
"INSERT INTO lieferant " +
"(l_nr, name, ort, plz) VALUES (?,?,?,?)");
// Parameter uebergeben
stmt.setInt(1, 103);
stmt.setString(2, "Fierz");
stmt.setString(3, "Bern");
stmt.setString(4, "3006");
// Befehl ausfuehren
int changes = stmt.executeUpdate();
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
...
9.1.3.4
Nullwerte
Um herauszufinden ob ein Attribut einen Nullwert enthält, muss zuerst das entsprechende Attribut mit der Methode getXXX gelesen werden. Anschliessend kann mit der Klassenmethode
ResultSet.wasNull() gefragt werden, ob das Resultat ein Nullwert ist.
Beispiel 9.4 [Nullwerte]
...
// Behandlung von Nullvalues
try {
Statement stmt = con.createStatement();
// Aufbauen eines Queries
ResultSet rs = stmt.executeQuery(
"SELECT p_nr, name, vater FROM person");
while (rs.next()) {
int vater = rs.getInt(3);
if (rs.wasNull() == true)
System.out.println("Person " + rs.getInt(1) + ". "
+ rs.getString(2) + " hat keinen Vater");
}
stmt.close();
9-9
} catch (Exception e) {
System.out.println(e);
}
...
9.1.3.5
Scrollen im Resultset
Bisher haben wir nach einem Query die Tuple im Resultset mit der Methode next() einfach
nacheinader eingelesen. Es besteht aber auch die Möglichkeit in einem Resultset zu “Scrollen”.
Beispiel 9.5 [Beispiel für Scrolling]
...
// Beispiel Scroll im Resultset
try {
Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
// Aufbauen eines Queries
ResultSet rs = stmt.executeQuery("SELECT * FROM lieferant " +
"ORDER BY l_nr");
// Lieferanten in der Umgekehrten Reihenfolge Ausgeben
rs.afterLast();
while (rs.previous()) {
System.out.print(rs.getString("name"));
}
// Nun Positionieren wir absolut und lesen wieder aufwaerts
rs.absolute(7);
do {
System.out.print(rs.getString("name"));
} while (rs.next());
// Statement explizit schliessen, damit die Datenbankresourcen
// sofort freigegeben werden und nicht erst bei einer Garbage// Collection.
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
...
9.1.3.6
Updates in einem Resultset
Die Tuple in einem Resultset können auch verändert werden. Man kann auch neue Tuple
einfügen und bestehende Tuple löschen. Das nächste Beispiel zeigt, wie man das tun kann.
Beispiel 9.6 [Update von Resultset]
...
// Beispiel Update und Insert im Resultset
try {
Statement stmt =
9-10
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
// Aufbauen eines Queries
ResultSet rs = stmt.executeQuery("SELECT * FROM lieferant " +
"ORDER BY l_nr");
// Setzen auf tupel sieben und Ort aendern
rs.absolute(7);
rs.updateString("Ort", "Genf");
rs.updateRow();
// Einfuegen eines Lieferanten
rs.moveToInsertRow();
// Daten abfuellen
rs.updateInt("l_nr", 201);
rs.updateString("name", "Fluri");
rs.updateString("ort", "Bern");
rs.updateString("plz", "3000");
// Lieferant zurueckschreiben
rs.insertRow();
// Auf Urspruengliche Position zurueck
rs.moveToCurrentRow();
// Statement explizit schliessen, damit die Datenbankresourcen
// sofort freigegeben werden und nicht erst bei einer Garbage// Collection.
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
...
9.1.4
SQL-ROUTINEN und JDBC
In diesem Abschnitt wollen wir noch sehen, wie Prozeduren und Funktionen aus einem JavaProgramm mit Hilfe von JDBC aufgerufen werden können. Zu diesem Zweck stellt JDBC
die Klasse CallableStatement zur Verfügung. Dabei ist es wichtig, dass der Datentyp der
Rückgabeparameter registriert wird.
9.1.4.1
Aufruf einer Prozedur
Die Syntax für den Aufruf einer Prozedur hat die folgende Form:
<jdbc-call> ::= {CALL <procname> ([<arg> [, <arg>]. . . ])}
<arg>
::= {? | <literal>}
Im nächsten Programmfragment wird die Prozedur bestellt aus dem Beispiel 8.1 aus einem
Java-Programm aufgerufen.
Beispiel 9.7 [Jdbc Prozeduraufruf]
9-11
...
try {
// Aufrufen der Prozedur "bestellt"
CallableStatement cstmt = con.prepareCall(
"{CALL bestellt(?, ?)}");
// Parameter nummer 1 setzen (Produktnummer)
cstmt.setInt(1,2);
// Rueckgabeparameter (Parameter 2) registrieren
cstmt.registerOutParameter(2, Types.INTEGER);
// Ausfuehren der Funktion
cstmt.execute();
// Lesen und ausgeben des Resultats
System.out.println(cstmt.getInt(2));
} catch (Exception e) {
System.out.println("*** " +e);
}
...
9.1.4.2
Aufruf einer Funktion
Die Syntax für den Aufruf einer Funktion hat die folgende Form:
<jdbc-funcall> ::= {? = CALL <procname> ([<arg> [, <arg>]. . . ])}
<arg>
::= {? | <literal>}
Im nächsten Programmfragment wird die Funktion fbestellt aus dem Beispiel 8.2 aus einem
Java-Programm aufgerufen. Man beachte, dass der Rückgabewert als Parameter nummer 1
behandelt wird. Da dies ein OUT-Parameter ist, muss er entsprechend registriert werden.
Beispiel 9.8 [Jdbc Funktionsaufruf]
...
try {
// Aufrufen der Funktion "fbestellt"
CallableStatement cstmt = con.prepareCall(
"{? = CALL fbestellt(?)}");
// Parameter nummer 2 setzen (Produktnummer)
cstmt.setInt(2,2);
// Rueckgabewert (Parameter 1) registrieren
cstmt.registerOutParameter(1, Types.INTEGER);
// Ausfuehren der Funktion
cstmt.execute();
// Lesen und ausgeben des Resultats
System.out.println(cstmt.getInt(1));
} catch (Exception e) {
System.out.println("*** " +e);
}
...
9-12
9.1.4.3
Prozeduren und Resultsets
Prozeduren können auch einen Resultset zurückgeben. Dazu genügt es in der Prozedur ein
Select-Statement zu schreiben. Das Resultat des Selects wird dann als Resultset zurückgegeben.
Beispiel 9.9 [Resultsets] Gegeben sei die folgende SQL Prozedur, die die Lieferanten mit
der AnzahlBestellung() auslistet
CREATE PROCEDURE
AnzahlBestellung()
BEGIN
SELECT
FROM
GROUP BY
l.l_nr, l.name, COUNT(*)
lieferant l natural join bestellung b
l.l_nr, l.name;
END
Nun kann die Prozedur AnzahlBestellung aufgerufen werden und der Resultset abgearbeitet
werden.
try {
// Aufrufen der Funktion "AnzahlBestellung"
CallableStatement cstmt =
con.prepareCall("{call AnzahlBestellung()}");
// Ausfuehren der Funktion
if (cstmt.execute()) {
// ResultSet hohlen und lesen
ResultSet rs = cstmt.getResultSet();
while (rs.next()) {
System.out.println(rs.getInt(1) + " " +
rs.getString(2) + " " +
rs.getInt(3));
}
}
} catch (Exception e) {
System.out.println("*** " +e);
}
9.1.4.4
Prozeduren mit mehreren Resulsets
In manchen DBMS (darunter auch Mysql) ist es möglich, dass eine Routine mehrere ResultSets
zurückgibt. Im nächsten Beispiel wird die Prozedur AnzahlBestellung() so ergänzt, dass sie
in einem zweiten ResultSet alle Lieferanten ohne Bestellungen zurückgibt.
Beispiel 9.10 [Multiple Resultsets]
CREATE PROCEDURE
AnzahlBestellung1()
BEGIN
– Erstes ResultSet
SELECT
l.l_nr, l.name, COUNT(*)
FROM
lieferant l natural join bestellung b
9-13
GROUP BYl.l_nr,
l.name;
– Zweites ResultSet
SELECT l.l_nr, l.name, 0
FROM lieferant l
WHERE NOT EXISTS (SELECT *
FROM bestellung b
WHERE b.l_nr = l.l_nr);
END
Das nächste Programmfragment ruft die Prozedur auf und listet anschliessend beide Resultate
aus.
...
try {
// Aufrufen der Funktion "AnzahlBestellung1"
CallableStatement cstmt = con.prepareCall(
"{call AnzahlBestellung1()}");
// Ausfuehren der Funktion
if (cstmt.execute()) {
do {
// ResultSet hohlen und lesen
ResultSet rs = cstmt.getResultSet();
while (rs.next()) {
System.out.println(rs.getInt(1) + " " +
rs.getString(2) + " " +
rs.getInt(3));
}
} while(cstmt.getMoreResults());
}
} catch (Exception e) {
System.out.println("*** " +e);
}
...
9.1.5
Meta-Informationen
Es gibt noch Datenbankprobleme, die nicht mit den bisherigen Klassen un Methoden gelöst
werden können. Diese Probleme tauchen dann auf, wenn die SQL-Statements nicht zur Kompilationszeit ermittelt werden können.
Beispiel 9.11 [Metadaten im Programm] Ein Beispiel eines solchen Programms kennen
wir bereits. Das Programm dbframe kennt die definitive Form der SQL-Statements erst zur
Laufzeit, da diese vom Benutzer erst dann eingegeben werden.
Das heisst, wir brauchen für die Systemprogrammierung noch weitere Instrumente:
1. Es muss möglich sein, zur Laufzeit den Aufbau eines ResultSet-Objektes zu interpretieren. Dazu steht uns die Klasse ResultSetMetaData zur Verfügung.
9-14
2. Es muss möglich sein, zur Laufzeit Informationen über die Datenbank (Schema, Attribute usw.) zu bekommen. Dazu steht uns die Klasse DatabaseMetaData zur Verfügung.
Bemerkung 9.3 [Vergleich embedded SQL] Die Klasse ResultSetMetaData liefert im
Prinzip dieselbe Funktionalität wie das dynamic SQL. Dynamic SQL ist das entsprechende
Instrument für Programme, die mit embedded SQL arbeiten.
9.1.5.1
Die Klasse ResultSetMetaData
Das nächste Beispiel zeigt, wie das ResultSet eines beliebigen Queries behandelt werden
kann. Die JDBC™Datentypen sind in der Klasse Types als Konstanten definiert.
Beispiel 9.12 [ResultsetMetaData]
...
// Beispiel fuer die Benutzung von ResultSetMetaData
try {
Statement stmt = con.createStatement();
// Aufbauen eines Queries
ResultSet rs = stmt.executeQuery(
"SELECT * FROM lieferant l, bestellung b " +
"WHERE l.l_nr = b.l_nr AND b.b_nr = 7");
// Metadaten hohlen
ResultSetMetaData rsm = rs.getMetaData();
// Anzahl Kolonnen bestimmen
int cols = rsm.getColumnCount();
while (rs.next()) {
// Name, Datentyp und Inhalt der einzelnen Kolonnen
// abfragen und ausgeben
for (int i = 1; i <= cols; i++) {
String cna = rsm.getColumnName(i),
cco = rs.getString(i);
int
cty = rsm.getColumnType(i);
System.out.println(cna + " (" + cty + "): " + cco);
}
}
stmt.close();
} catch (Exception e) {
System.out.println(e);
}
...
9.1.5.2
Die Klasse DatabaseMetaData
Das nächste Beispiel zeigt, wie Informationen über die einzelnen Relationenschematas der
Datenbank gewonnen werden kann.
Beispiel 9.13 [Beispiel DatabaseMetaData]
9-15
...
// Beispiel fuer die Benutzung von DatabaseMetaData
try {
DatabaseMetaData dbmd = con.getMetaData();
// Information ueber alle Tabellen
String [] s = {"TABLE"};
ResultSet rs1 = dbmd.getTables(null, null, "%", s);
while (rs1.next()) {
String tn = rs1.getString("TABLE_NAME");
// Einlesen des Primaerschluessels
ResultSet rs2 = dbmd.getPrimaryKeys(null, null, tn)
System.out.print(tn + " Primary(");
// Ausgeben der Attribute des Primaerschluessels
while (rs2.next()) {
System.out.print(getString("COLUMN_NAME") + ",");
}
System.out.println(")\n=========================");
// Einlesen aller Attribute der Tabelle
ResultSet rs2 = dbmd.getColumns(null, null, tn, null);
while (rs2.next()) {
// Ausgeben einiger Informationen
System.out.println(rs2.getString("COLUMN_NAME") +
" / " + rs2.getString("TYPE_NAME") +
"(" + rs2.getInt("COLUMN_SIZE") + ")");
}
}
} catch (Exception e) {
System.out.println(e);
}
...
9.1.5.3
Die execute Methode
Falls nicht im Voraus bekannt ist, ob ein SQL-Statement ein Query- oder ein Update-Statement
ist, so sollte die execute Methode zur Durchführung angewendet werden. Der Rückgabewert
der Methode ist true, falls ein Query uebergeben wurde und false sonst. Falls ein Query
übergeben wurde, kann mit der Methode getResulSet() das eigentliche Resultat geholt werden, sonst mit getUpdateCount() die Anzahl veränderter Tuple.
Beispiel 9.14 [Executemethode] Im folgenden Beispiel kann ein beliebiges SQL-Statement
auf der Kommandozeile übergeben werden.
...
// Beispiel fuer die Benutzung von execute und
// ResultSetMetaData
try {
Statement stmt = con.createStatement();
// SQL-Statement ist im 3. Argument der Kommandozeile:
9-16
// Ausfuehren des Statements
if (stmt.execute(args[2])) {
// Das Statement ist ein Query wir muessen also
// die Resultset und die Metadaten hohlen
ResultSet rs = stmt.getResultSet();
ResultSetMetaData rsm = rs.getMetaData();
// Anzahl Kolonnen bestimmen
int cols = rsm.getColumnCount();
// Loop ueber alle gefundenen Tuple
while (rs.next()) {
// Name, Datentyp und Inhalt der einzelnen Kolonnen.
for (int i = 1; i <= cols; i++) {
String cna = rsm.getColumnName(i),
cco = rs.getString(i);
int
cty = rsm.getColumnType(i);
System.out.println(cna + " (" + cty + "): " + cco);
}
}
} else {
// Kein Query anzahl veraenderter Tuple herausgeben
System.out.println("Update Count: " + stmt.getUpdateCount());
}
stmt.close();
} catch (Exception e) {
// Fehler irgendwelcher Art
System.out.println(e);
}
...
9.2
Embedded SQL
Eine mögliche Koppelung zwischen einer Wirtssprache und einer Datenbank ist embedded
SQL. In die Wirtssprache werden SQL-Befehle eingebettet und klar gekennzeichnet. Diese
Befehle werden von einem Precompiler in Aufrufe von Kommunikationsprozeduren übersetzt.
Bemerkung 9.4 [SQL/92 Standard] Embedded SQL ist bestandteil vom SQL/92 Standard.
9.2.1
Syntax von embedded SQL-Statements
Die Syntax eines embedded SQL-Statements sieht wie folgt aus:
<embedded-statement> ::=
EXEC SQL
<sql-befehl> <terminator>
Bemerkung 9.5 [esql]
• EXEC SQL ist als Ganzes ein Keywort und muss auf einer Zeile vor dem SQL-Statement
stehen.
9-17
• Der Terminator ist von der benutzten Wirtssprache abhängig. In der Sprache C ist dies
wie üblich “;”.
• Das SQL-Statement kann über mehrere Zeilen fortgesetzt werden, dabei müssen die
Regeln der Wirtssprache eingehalten werden.
• Kommentare in einem SQL-Statement müssen der Syntax der Wirtssprache genügen.
Beispiel 9.15 [Selektieren] Das nachfolgende Beispiel selektiert die Namen der Lieferanten,
die in Bern wohnen und legt diese in die Variable name der Wirtssprache ab.
EXEC SQL SELECT
name
:name
FROM lieferant
WHERE l_nr = 1;
INTO
Allen Variablen der Wirtssprache muss im SQL-Befehl ein ’:’ vorangestellt werden.
9.2.2
Aufbau eines C-Programms mit embedded SQL
In diesem Abschnitt wollen wir die wichtigsten Komponenten eines C-Programms mit embedded SQL kennenlernen.
9.2.2.1
Deklaration
In dieser Sektion müssen alle Variablen definiert werden, die von der Wirtssprache und von
SQL-Befehlen verwendet werden. Es kann mehrere solcher Sektionen geben, z.B. eine lokale
innerhalb einer Prozedur und eine Globale ausserhalb der main() Funktion. Die Deklaration
ist gemäss der Syntax der Wirtssprache vorzunehmen.
EXEC SQL BEGIN DECLARE SECTION;
char name[31];
char *db_name = "my_database";
EXEC SQL END DECLARE SECTION;
Der Gültigkeitsbereich der Variablen (Scope) wird von den Regeln der Wirtssprache diktiert.
9.2.2.2
Status und Fehler
Das folgende Statement definiert eine globale SQL-Communication-Area (als C-struct) für die
Übergabe von Kontrollinformation zwischen der Datenbank und dem C-Programm. Gewisse
Preprozessoren brauchen diese Anweisung nicht und hohlen die entsprechende Struktur durch
einen normalen C-Include.
EXEC SQL INCLUDE
SQLCA;
Die definierte Struktur heisst dann sqlca und die wichtigsten Komponenten sind:
9-18
sqlca.sqlstate Dies ist ein String der Länge 5. Die zwei ersten Zeichen bezeichnen die Fehlerklasse und die 3 folgenden Zeichen die Fehlersubklasse. Nachfolgend einige Beispiele.
Die vollständige Liste der Fehler kann im SQL-Standard oder in der Beschreibung der
entsprechenden Datenbank gefunden werden.
Klasse
00
01
01
02
02
08
08
08
08
Subklasse
000
00C
004
000
001
000
003
006
Bedeutung
Erfolgreich
Warnings
DYNAMIC RESULT SETS RETURNED
STRING DATA RIGHT TRUNCATION
usw.
Keine Daten verarbeitet
NO DATA
NO ADDITIONAL DYNAMIC RESULT SETS RETURNED
Connection Exception
CONNECTION EXCEPTION
CONNECTION DOES NOT EXIST
CONNECTION FAILURE
usw.
sqlca.sqlerrm Enthält die Fehlermeldung im Klartext. “sqlerrm” ist selbst eine Struktur und
ist als
struct {
short sqlerrml;
char sqlerrmc[70];
} sqlerrm;
definiert. Achtung: das Feld “sqlerrmc” ist nicht Null-terminiert.
sqlca.sqlerrd[1] Enthält die Fehlernummern des Datenbank-Management-Systems.
sqlca.sqlerrd[3] Enthält die Anzahl von einem SQL-Befehl selektionierter oder bearbeiteter
Datentupel.
9.2.2.3
Installieren der Fehlerhandler
Mit dem WHENEVER Befehl wird definiert, welche Routine bei einem Fehler (oder einem
Warning) aufgerufen werden soll. Der entsprechende Handler kann dann entscheiden, was
zu tun ist. Fehlerhandler werden wir im Abschnitt über Prozeduren näher behandeln (siehe
Kapitel 8).
9.2.2.4
Verbindung zur Datenbank aufnehmen
Das nächste Statement ist obligatorisch und bewirkt den Aufbau einer Verbindung zum
DBMS. Nach diesem Statement können beliebige SQL- Befehle abgesetzt werden.
EXEC SQL CONNECT TO
<dbname>
USER
9-19
<username>
IDENTIFIED BY
<password>;
9.2.2.5
Programm
Nachdem die Verbindung zur Datenbank aufgenommen worden ist, können beliebige SQLBefehle und Befehle der Wirtssprache verwendet werden.
9.2.2.6
Beenden der Verbindung zur Datenbank
Die Verbindung zur Datenbank wird mit dem DISCONNECT Befehl beendet. Nach diesem
Befehl können keine weiteren SQL-Befehle abgesetzt werden.
EXEC SQL DISCONNECT;
Beispiel 9.16 [Programmbeispiel] Für das Beispiel beziehen wir uns wieder auf unsere
Lieferanten-Datenbank. Wir wollen den Namen des Lieferanten mit Nummer 8 auf dem Bildschirm ausgeben.
#include <stdio.h>
#include <stdlib.h>
#include "sqlerror.h"
main(int argc, char **argv) {
/* Deklarationen */
EXEC SQL BEGIN DECLARE SECTION;
char name[31];
char *db_name, *user;
EXEC SQL END DECLARE SECTION;
EXEC SQL WHENEVER SQLERROR CALL error_handler();
EXEC SQL WHENEVER SQLWARNING CALL warning_handler();
EXEC SQL WHENEVER NOT FOUND SQLPRINT;
/* Verbindung zur Datenbank aufnehmen */
if (argc < 2) {
fprintf(stderr, "usage: %s user database\n", argv[0]);
exit(0);
}
user = argv[1];
db_name = argv[2];
EXEC SQL CONNECT TO :db_name USER :user;
/* Selektieren und ausgeben */
EXEC SQL SELECT name INTO :name
FROM lieferant
WHERE l_nr = 8;
printf("%s\n", name);
9-20
/* Verbindung schliessen */
EXEC SQL DISCONNECT;
}
Bemerkung 9.6 [Select ohne Cursor] Der Select-Befehl funktioniert in diesem Beispiel
nur, weil nur genau 1 Lieferant gefunden wird. Um zum Beispiel alle Lieferanten von Bern
auszugeben ist ein Cursor notwendig.
9.2.3
Cursor
Cursor sind ein wichtiges Konzept bei der Einführung prozeduraler Elemente in SQL. Das Resultat einer SQL-Abfrage ist im allgemeinen eine Menge von Tupeln (Relation). Wirtssprachen
wie C oder Pascal sind aber nicht zum Verarbeiten von Mengen ausgerüstet. Ein Cursor erlaubt es nun, die von einem SELECT-Befehl ausgewählten Datentupel in der Wirtssprache
einzeln zu verarbeiten. Dabei können die Tupel gelesen, verändert oder gelöscht werden.
Die Deklaration eines Cursors besteht aus der Angabe eines Cursor-Namens und einem zugeordneten SELECT-Befehl. Die Deklaration hat noch keinerlei Auswirkungen auf die Datenbank
zur Folge. Erst wenn ein OPEN <cursorname> durchgeführt wird, übersetzt das DBMS den
SELECT- Befehl und positioniert den Cursor vor das erste Resultat-Tupel. Mit dem FETCHBefehl können die Tupel nacheinander in das Anwendungsprogramm übertragen werden. Der
CLOSE-Befehl gibt den Cursor frei.
Deklaration des Cursors
Die allgemeine Syntax für die Deklaration eines Cursors lautet:
<declare-cursor> ::=
<cursor-name> CURSOR FOR
<select-befehl>
[FOR UPDATE [OF <attribut-list>]]
DECLARE
Im SELECT-Befehl des Cursors können natürlich auch Variablen der Wirtssprache verwendet
werden.
Beispiel 9.17 [Cursor] Wir wollen einen Cursor definieren, der dazu dient, alle Lieferanten
zu finden, deren Wohnort in der Variable ort gespeichert ist. Die Tupel sollen nach Namen
sortiert eingelesen werden.
lieferant_cursor
name
FROM
lieferant
WHERE
ort = :ort
ORDER BY name;
EXEC SQL DECLARE
CURSOR FOR
SELECT
Will man die vom Cursor selektierten Tupel verändern, so müssen die Attribute, die verändert
werden sollen, in der FOR UPDATE-Klausel angegeben werden. Wird in der FOR UPDATEKlausel keine Attributliste angegeben, so können alle Attribute der Relation verändert werden.
Bemerkung 9.7 [Update mit Cursor] Die Tupel eines Cursors können nur dann verändert
oder gelöscht werden, wenn in der FROM-Klausel eine Basisrelation oder eine veränderbare
View angegeben wird. Wird in der FROM-Klausel mehr als eine Tabelle angegeben, so können
9-21
die Tupel des Cursors im allgemeinen nicht verändert oder gelöscht werden (siehe auch Kapitel
??).
Beispiel 9.18 [Ändern der PLZ] In diesem Beispiel deklarieren wir einen Cursor, um die
Postleitzahlen der Lieferanten zu ändern.
lieferant_cursor
name
FROM
lieferant
WHERE
ort = :ort
ORDER BY
name;
FOR UPDATE OF plz;
EXEC SQL DECLARE
CURSOR FOR
SELECT
9.2.3.1
Öffnen des Cursors
Die allgemeine Syntax für das Öffnen eines Cursors lautet:
<open-cursor> ::=
OPEN
<cursorname>;
Wie schon erwähnt, wird ein DECLARE CURSOR Befehl nicht ausgeführt. Erst wenn der Cursor
geöffnet wird, werden die durch den SELECT-Befehl definierten Tupel aus der Datenbank
selektiert.
Bemerkung 9.8 [Select mit Cursor] Der OPEN-Befehl selektiert alle Daten. Kommen in
den Selektionskriterien Variablen der Wirtssprache vor, so gilt deren Wert zur Zeit des OPENBefehls. Solange der Cursor also offen bleibt, kann die Menge der selektierten Tupel nicht
mehr verändert werden, auch dann nicht, wenn der Wert der entsprechenden Variablen der
Wirtssprache verändert wird.
Beispiel 9.19 [Cursor aktivieren] Wir wollen den oben definierten Cursor Lieferant_cursor
nun aktivieren.
EXEC SQL OPEN
lieferant_cursor;
Nach diesem Befehl ist noch kein Tupel eingelesen. Der Cursor ist vor dem ersten selektierten
Tupel positioniert.
Einlesen der einzelnen Tupel Die allgemeine Syntax für das Einlesen eines Tupels mit
Hilfe eines offenen Cursors lautet:
<fetch-tupel> ::= FETCH [[<orientation>] FROM] <cursorname>
<orientation>::= NEXT
|
PRIOR
|
FIRST
|
LAST
|
{ABSOLUTE|RELATIVE} <value>
INTO
<variablen-list>
Mit dem FETCH-Befehl wird das nächste selektierte Tupel eingelesen, dabei muss angegeben
werden, in welchen Variablen die gefundenen Werte abgelegt werden müssen.
Beispiel 9.20 [Tuple eines Cursors einlesen] Nachfolgend ist ein Programmausschnitt,
der die Namen der Lieferanten aus Bern mit Hilfe eines Cursors ausgibt.
9-22
...
/* Deklaration des Cursors (keine Selektion) */
EXEC SQL DECLARE my_cursor CURSOR FOR
SELECT l_nr, name
FROM
lieferant
WHERE ort = :ort
ORDER BY name;
/* Abfragen des Ortes */
printf("Ort eingeben: ");
fgets(ort, 30, stdin);
/* Oeffnen des Cursors. Die Daten werden mit dem
aktuellen Wert in der Variable "ort" selektiert. */
EXEC SQL OPEN my_cursor;
/* Tupel nacheinander einlesen und ausgeben */
EXEC SQL FETCH my_cursor INTO :l_nr, :name;
while (strcmp(sqlca.sqlstate, "00000") {
printf("%d %s\n", l_nr, name);
EXEC SQL FETCH my_cursor INTO :l_nr, :name;
}
/* Cursor und Verbindung schliessen */
EXEC SQL CLOSE my_cursor;
...
9.2.3.2
Behandlung von Nullwerten
Häufig muss man nach einem FETCH-Befehl wissen, ob in einem Feld in der Datenbank ein
Nullwert gespeichert ist. Dies kann mit Hilfe einer Indikatorvariablen geschehen. Indikatorvariablen müssen als short int in einer Declare-Sektion deklariert werden. Beim FETCH-Befehl
werden sie durch ’:’ getrennt direkt nach der zu testenden Variablen angefügt.
Beispiel 9.21 [Indikatorvariable] Im unserer Datenbank wollen wir bei Bestellungen testen,
ob das Lieferdatum einen Nullwert enthält.
bestellung CURSOR FOR
lieferdatum
FROM bestellung;
OPEN bestellung;
FETCH bestellung INTO :lieferdatum:ind_datum;
EXEC SQL DECLARE
SELECT
EXEC SQL
EXEC SQL
Ist nun im Lieferdatum ein Nullwert, so enthält die Variable ind_datum nach dem FETCHBefehl den Wert -1 und der Wert der Variablen lieferdatum wird nicht verändert. Sonst ist
der Wert der Variablen ind_datum gleich 0.
Bemerkung 9.9 [Fehler bei Nullwerten] Falls für ein gelesenes Attribut im FETCH Befehl
keine Indikatorvariable angegeben ist und ein Tupel mit einem Null-Wert in diesem Attribut
selektiert wird, so wird ein Fehler generiert und das Tupel wird nicht eingelesen.
9-23
9.2.3.3
Ändern eines Tupels
Die allgemeine Syntax für das Ändern eines Tupels mit Hilfe eines Cursors lautet:
<cursor-update> ::=
<table>
<assignment-list>
UPDATE
SET
WHERE CURRENT OF
<cursor-name>
Bemerkung 9.10 [Ändern von Tupels] Es wird das Tupel an der aktuellen Cursorposition
verändert. Die Position des Cursors wird durch den UPDATE-Befehl nicht verändert.
Alle Attribute in der assignment-list müssen in der Deklaration des Cursors in der FOR UPDATE-Klausel angegeben sein. Ferner darf kein Feld verändert werden, das in der ORDER
BY-Klausel des entsprechenden Cursors vorkommt.
Beispiel 9.22 [Ändern der PLZ] In diesem Beispiel wird bei allen Lieferanten mit Postleitzahl
3001 dieser Wert auf 3002 geändert.
...
/* Deklaration des Cursors (keine Selektion) */
EXEC SQL DECLARE my_cursor CURSOR FOR
SELECT
l_nr, name, plz
FROM
lieferant
WHERE
plz = :plz
FOR UPDATE OF plz;
printf("Alte PLZ: ");
fgets(plz, 8, stdin);
rclip(plz);
printf("Neue PLZ: ");
fgets(plz1, 8, stdin);
rclip(plz1);
/* Oeffnen des Cursors. Die Daten werden mit dem
aktuellen Wert in der Variable "plz" selektiert. */
EXEC SQL OPEN my_cursor;
EXEC SQL FETCH my_cursor INTO :l_nr, :name, :plz;
while (strcmp(sqlca.sqlstate, "00000") == 0) {
/* Aendern der Postleitzahl auf den neuen Wert
fuer den aktuellen Lieferant */
EXEC SQL UPDATE lieferant
SET
plz = :plz1
WHERE CURRENT OF my_cursor;
printf("Lieferant: %d %s PLZ von %s auf %s geaendert\n",
l_nr, name, plz, plz1);
EXEC SQL FETCH my_cursor INTO :l_nr, :name, :plz;
}
EXEC SQL CLOSE my_cursor;
9-24
EXEC SQL COMMIT;
...
9.2.3.4
Löschen eines Tupels
Die allgemeine Syntax für das Löschen eines Tupels mit Hilfe eines Cursors lautet:
<cursor-delete> ::=
DELETE
FROM
<table>
WHERE CURRENT OF
<cursorname>
Bemerkung 9.11 [Löschen] Es wird das Tupel an der aktuellen Cursorposition gelöscht.
Nach dem Löschen ist der Cursor vor dem Tupel, das dem gerade gelöschten Tupel folgt,
positioniert. Existiert das folgende Tupel nicht, so ist der cursor nach dem letzten Tupel
positioniert.
Beispiel 9.23 [Löschen von Tupels] Im obigen Programm könnten wir also alle Lieferanten
mit Postleitzahl 3001 löschen, indem wir den UPDATE-Befehl durch den folgenden DELETEBefehl ersetzen.
DELETE
FROM
lieferant
WHERE CURRENT OF
9.2.3.5
lieferant_cursor;
Schliessen des Cursors
Die allgemeine Syntax für das Schliessen eines Cursors lautet:
<close-cursor> ::=
CLOSE
<cursorname>
Ein Cursor kann beliebig oft geschlossen und wieder geöffnet werden. Jedesmal wenn der
Cursor wieder geöffnet wird, werden die Tupels neu von der Datenbank selektiert. Mit Hilfe von
Variablen der Wirtssprache kann also derselbe Cursor für verschiedene Selektionen verwendet
werden.
Bemerkung 9.12 [Cursor Name] Der Cursor Name ist eine Vereinbarung zwischen dem
Datenbanksystem und einem Prozess. Das heisst, der Name des Cursors ist innerhalb eines
Programms global gültig.
9.2.3.6
Verarbeiten von rekursiven Datenstrukturen
Als letztes wollen wir noch ein Beispiel für das Verarbeiten von rekursiven Datenstrukturen
mit embedded SQL betrachten.
Beispiel 9.24 [Nachkommen bestimmen] Wir betrachten das nachfolgende Relationenschema person, welches die Eltern-Kinder-Beziehung darstellt.
9-25
CREATE TABLE
p_nr
name
vorname
mutter
vater
person (
INTEGER PRIMARY KEY,
VARCHAR(30) NOT NULL,
VARCHAR(30) NOT NULL,
INTEGER REFERENCES
INTEGER REFERENCES
person(p_nr),
person(p_nr)
)
Die folgende Prozedur ermittelt alle Nachkommen einer gegebenen Person mit Hilfe von embedded SQL. Die direkten Nackommen werden auf einem Stack abgelegt. Anschliessend werden die Nachkommen der Nachkommen auf den Stack abgelegt usw.
static void nachkommen(int person) {
EXEC SQL BEGIN DECLARE SECTION;
char name[31];
char vorname[31];
int act_pers;
int levelcount;
EXEC SQL END DECLARE SECTION;
EXEC SQL WHENEVER SQLERROR CALL error_handler();
EXEC SQL WHENEVER SQLWARNING CALL warning_handler();
EXEC SQL WHENEVER NOT FOUND CONTINUE;
int i, level = 0, sp = -1, tmp;
int st[1000];
getdirect(&sp, st, person, &level);
while (sp >= 0) {
while (sp >= 0 && st[sp] < 0) {
--sp;
--level;
}
if (sp >= 0) {
act_pers = st[sp--];
EXEC SQL SELECT name, vorname into :name, :vorname
FROM person
WHERE p_nr = :act_pers;
for (i = 1; i < level; i++) {
printf("
");
}
printf("%d.) %d %s %s\n", level, act_pers, rclip(name),
rclip(vorname));
getdirect(&sp, st, act_pers, &level);
}
}
9-26
}
static void getdirect(int *sp, int *st, int person, int *level) {
EXEC SQL BEGIN DECLARE SECTION;
int act_pers;
EXEC SQL END DECLARE SECTION;
EXEC SQL WHENEVER SQLERROR CALL error_handler();
EXEC SQL WHENEVER SQLWARNING CALL warning_handler();
EXEC SQL WHENEVER NOT FOUND CONTINUE;
/* Kreieren eines Cursors fuer die aktuelle Stufe */
EXEC SQL DECLARE curs_name CURSOR FOR
SELECT p_nr
FROM
person
WHERE mutter = :act_pers OR
vater = :act_pers;
act_pers = person;
EXEC SQL OPEN curs_name;
EXEC SQL FETCH curs_name INTO :act_pers;
if (strcmp(sqlca.sqlstate, "00000") == 0) {
st[++(*sp)] = -1;
++(*level);
}
while (strcmp(sqlca.sqlstate, "00000") == 0) {
st[++(*sp)] = act_pers;
EXEC SQL FETCH curs_name INTO :act_pers;
}
EXEC SQL CLOSE curs_name;
}
9-27
9.3
9.3.1
Übungen
Arbeiten mit JDBC™
Sie finden Sie das Programmbeispiel BspJDBC.java aus dem Skript auf meiner Internetseite
staff.hti.bfh.ch/fep1 unter Datenbanken.
Um das Programm auszuführen müssen Sie im Classpath den Mysql-Driver angeben. Der
Driver kann auch von meiner Internetseite geladen werden.
Zum Starten des Programms
# javac BspJDBC.java
# java -cp .:mysql-connector-java-3.1.13.jar BspJDBC \
<user> <passwort> <datenbank>
9.3.2
JDBC™Aufgaben
Aufgabe 9.1 [Finden der Vorfahren] Schreiben Sie ein Programm, das alle Vorfahren
einer Person in der Datenbank findet und am Bildschirm ausgibt.
Es soll auch möglich sein, alle Personen auszulisten, die in der Datenbank überhaupt Vorfahren haben und dann eine dieser Personen auszuwählen.
Aufgabe 9.2 [Nachfahren] Ergänzen Sie das Programm in der Aufgabe 9.1 so, dass der Benutzer nach der Auswahl einer Person wählen kann, ob er die Nachfahren oder die Vorfahren
dieser Person sehen will.
Aufgabe 9.3 [Fremdschlüssel] Schreiben Sie ein Programm, das als Input eine Datenbanktabelle hat und als Output alle Fremdschlüssel dieser Tabelle ausgibt.
9-28
9-29
Kapitel 10
Datenintegrität
(Transaktionstheorie)
Im allgemeinen arbeiten viele Benutzer mit derselben Datenbank. Eine vordringliche Aufgabe
des DBMS besteht darin, die Lese- und Schreiboperationen der verschiedenen Prozesse zu koordinieren. Die Datenbank muss einerseits so erscheinen, wie wenn sie jedem Benutzer gehörte
(sequentieller Ablauf aller Prozesse), andererseits sollten die Lese- und Schreiboperationen so
organisiert sein, dass kein Benutzer lange warten muss. Ferner muss das DBMS dafür sorgen,
dass nach einem Systemabsturz oder Diskcrash die Daten wieder in einem konsistenten Zustand gebracht werden können. Dies soll ohne Verlust von wichtigen Informationen geschehen
(Recovery).
10.1
Konsistenz einer Datenbank
Eine wesentliche Anforderung an eine Datenbank ist, dass sie sich jederzeit in einem konsistenten Zustand befindet.
Definition 10.1 [Konsistenz] Eine Datenbank ist konsistent, wenn
1. alle Integritätsbedingungen erfüllt sind. Dazu gehören die Primärschlüsselintegrität und alle referentiellen und semantischen Integritätsbedingungen.
2. alle Datenwerte mit der gegenwärtigen Realität übereinstimmen.
3. alle relevanten Daten vollständig in der Datenbank vorhanden sind.
Die Verletzung der ersten Bedingung hat meistens technische Ursachen, z.B.
• Systemabsturz, Disk-Crashes, Hardware-Fehler usw.
• Fehler in den Anwendungsprogrammen, die zum Abbruch von laufenden Operationen
in der Datenbank führen.
• Gleichzeitiges Modifizieren/Lesen derselben Daten durch mehrere Benutzer.
Um die Konsistenzbedingungen einhalten zu können, wurde das Transaktionsmodell entworfen.
Die Verantwortung für die Bedingungen zwei und drei liegen beim Anwender der Datenbank
und nicht beim DBMS.
10-1
10.2
Das Transaktionsmodell
Als erstes wollen wir den Begriff Transaktion definieren.
Definition 10.2 [Transaktion] Eine Transaktion ist eine inhaltlich zusammengehörende Serie von Lese- und Schreiboperationen, welche die Datenbank von einem
konsistenten Zustand in einen anderen konsistenten Zustand überführen.
Eine Transaktion hat explizit definierte Grenzen, die in der Regel durch die Applikation festgelegt werden. Durch den ersten SQL Befehl wird die Transaktion gestartet und durch einen
COMMIT-Befehl abgeschlossen. In Fehlersituationen kann eine Transaktion mit dem ROLLBACKBefehl abgebrochen werden. In diesem Fall ist das DBMS dafür verantwortlich, dass alle
Änderungen an den Daten seit Beginn der Transaktion rückgängig gemacht werden.
Von Transaktionen werden vier Eigenschaften verlangt, die unter dem Kürzel ACID zusammengefasst sind.
Atomarität Eine Transaktion führt entweder alle gewünschten Update-Operation durch oder
keine (“Alles oder Nichts”-Prinzip).
Konsistenz Ein konsistenter Zustand wird immer in einen neuen, konsistenten Zustand überführt. Während der Transaktion dürfen inkonsistente Zustände auftreten. Alle Konsistenzbedingungen müssen spätestens bei Transaktionsabschluss erfüllt sein.
Isolation Änderungen der Daten während der Transaktion sind für andere Benutzer nicht
sichtbar.
Dauerhaftigkeit Nach Abschluss einer Transaktion müssen alle Änderungen auf einem sicheren
Speichermedium sein und damit dauerhaft festgehalten sein.
Alle Forderungen könnten leicht erfüllt werden, wenn die Transaktionen nur seriell ablaufen
würden. Dies entspricht jedoch nicht der Vorstellung einer Mehrbenutzer Datenbank, da bei
langen Transaktionen das System für andere Benutzer auch lange blockiert sein würde. Ferner tritt sehr häufig der Fall auf, dass die Benutzer gar nicht die gleichen Daten lesen und
schreiben. In diesem Fall können Transaktionen natürlich problemlos parallel ablaufen. Probleme treten nur dann auf, wenn zwei Benutzer gleichzeitig auf die gleichen Daten zugreifen
wollen (concurrency Problem).
Bemerkung 10.1 [Serialisierbarkeit] Zu den vier oben beschriebenen Eigenschaften einer
Transaktion wird manchmal noch die sogenannte Serialisierbarkeit gefordert:
Laufen mehrere Transaktionen parallel ab, so muss die Auswirkung bezüglich Änderungen
und Ausgabe dieselbe Wirkung haben, wie wenn alle Transaktionen streng nacheinander ausgeführt worden wären.
10-2
10.3
Atomarität und Dauerhaftigkeit
10.3.1
Das Logfile
Die “Atomarität” verlangt, dass entweder alle Updates einer Transaktion durchgeführt werden oder keine. Diese Forderung wird mit Hilfe eines datenbankweiten Transaktions-Logfile
realisiert. Im Logfile werden folgende Informationen festgehalten:
• Begin einer Transaktion
• UPDATE-, INSERT- und DELETE-Operationen. Dabei werden sämtliche alten und neuen
Datenwerte sowie die Transaktion, zu der sie gehören, festgehalten.
• Ende einer Transaktion.
Bemerkung 10.2 [Write-Ahead] Alle Änderungen müssen auf das Logfile übertragen werden bevor die Änderungen in der Datenbank nachgeführt werden dürfen. Diese Regel heisst
Write-Ahead log Protocol.
Logfile-Einträge werden ohne Write-Buffer oder Cache-Speicher durchgeführt, damit sie bei
einem Prozess-Crash des DBMS nicht verlorengehen. Das Logfile befindet sich auf einem
sicheren Speichermedium. Als solches bezeichnet man je nach Anforderungen eine einfache
Disk oder eine 1 bis n mal gespiegelte Disk.
Die Einträge im Logfile genügen nun, um die Atomarität zu gewährleisten. Wird ein ROLLBACKBefehl abgesetzt, so können dank den alten Datenwerten im Logfile alle Änderungen rückgängig gemacht werden.
10.3.2
Checkpoints
Nachdem die ’Commit’-Markierung im Logfile festgehalten ist, gilt eine Transaktion als logisch abgeschlossen. Theoretisch könnten jetzt alle Daten dieser Transaktion in die Datenbank
zurückgeschrieben werden. Aus Performance-Gründen hält man aber alle Buffer-Pages solange
im Hauptspeicher wie möglich, so dass diese sofort wieder für andere Transaktionen zur Verfügung stehen. Das effektive Zurückschreiben von Daten auf die Disk versucht man möglichst
hinauszuzögern. Das Zurückschreiben der Daten auf die Disk wird in zwei Fällen ausgelöst:
1. Der Cache-Buffer wird für neue Daten, die von der Disk eingelesen werden, benötigt.
In diesem Fall werden die am längsten nicht mehr benutzten Daten zurückgeschrieben.
Diese dynamische Bufferverwaltung wird vom DBMS automatisch durchgeführt.
2. Beim Erreichen eines Checkpoints. In diesem Fall werden alle modifizierten Daten auf
die Disk zurückgeschrieben. Checkpoints werden vom System automatisch ausgelöst z.B.
nach Ablauf einer bestimmten Zeit oder nach Erreichen einer bestimmten Anzahl modifizierter Datentupel. Je nach Datenbanksystem können diese Parameter vom Administrator eingestellt werden. Während dem Zurückschreiben sind laufende Änderungsoperationen blockiert. Checkpoints werden im Logfile festgehalten (nach dem Zurückschreiben
der Buffer) und enthalten die Identifikation der gerade laufenden Transaktionen.
Checkpoints sind von ausschlaggebender Bedeutung beim Hochfahren eines unkontrolliert
abgestürzten Datenbanksystems. Sie markieren einen genau definierten Systemzustand, nämlich den, wo jede im Logfile protokolierte Änderung auch in den Datenbankfiles auf der Disk
festgehalten ist.
10-3
10.3.3
Recovery
Mit Hilfe des Logfiles (mit Checkpoints) kann das Prinzip der Dauerhaftigkeit eingehalten
werden. Wir betrachten dazu die Abbildung 10-1, die den zeitlichen Verlauf von fünf parallelen
Transaktionen darstellt:
Werden nochmals ausgefuehrt
Werden rueckgaengig gemacht
A1
T1
A2
A1
T2
A2
A3
A1
T3
A1
T4
A2
A1
T5
Zeit
Checkpoint
Crash
Logfile−Eintraege:
BOT
T1
BOT
A1
BOT
A1
A2
A1
EOT
T4
T1
T2
T4
T1
T2
T1
A2
Checkpoint
aktiv
T2
T2,T4
BOT
A3
A1
A2
BOT
A1
EOT
T3
T2
T3
T4
T5
T5
T3
BOT
A3
A1
A2
BOT
A1
EOT
Recovery Operationen auf dem Logfile:
BOT
A1
Undo
Undo
T4
T4
Redo Redo Redo Undo Undo Undo Redo
T3
BOT = Begining of Transaction
T2
T3
T4
T5
T5
T3
EOT = End of Transaction
Tx = Transaktionen T1 bis T5
Ax = Aenderungs−Aktion einer Transaktion
Abbildung 10-1: Recovery mit Hilfe des Logfiles
Transaktion T1 Diese Transaktion wurde vor dem letzten Checkpoint abgeschlossen. Wir
können also sicher sein, dass all ihre Änderungen in der Datenbank gespeichert sind.
Transaktion T2 Diese Transaktion ist logisch abgeschlossen. Wir wissen ferner, dass die
Operationen A1 und A2 sicher in der Datenbank gespeichert sind. Für die Operation
A3 muss eine REDO-Operation ausgeführt werden.
Transaktion T3 Diese Transaktion ist logisch abgeschlossen. Wir wissen nur, dass alle Operationen im Logfile stehen. Um die Transaktion T3 beim Recovery zu kompletieren,
müssen wir auf Grund der im Logfile gespeicherten neuen Datenwerte für die Operation
A1 eine REDO-Operation ausführen.
Transaktion T4 und T5 Diese Transaktionen sind nicht abgeschlossen und würden die
Datenbank in einem inkonsistenten Zustand hinterlassen. Beim Recovery müssen daher
10-4
alle Operationen dieser beiden Transaktionen mit einer UNDO-Operation rückgängig
gemacht werden.
10.4
Zwei Phasen Commit (two-phase commit)
Das oben beschriebene Verfahren zur Implementation der ’Atomarität’ und ’Dauerhaftigkeit’
genügt nicht, wenn die Datenbank verteilt ist. D.h., die Daten sind nicht alle auf derselben
Datenbank gespeichert. Diese Datenbanken können auch auf mehreren Maschinen verteilt
sein. Um auch hier die ’Atomarität’ und ’Dauerhaftigkeit’ zu gewährleisten wird das sogenannte zwei Phasen Commit Protokol verwendet. Ein COMMIT oder ROLLBACK Befehl muss
systemweit an alle beteiligten Datenbanken abgegeben werden. Diese Aufgabe wird vom Koordinator übernommen. Der Koordinator ist der Initiator der verteilten Transaktion. Zusätzlich
zur Identifikation der Transaktion muss jeder Beteiligte Knoten auch den Koordinator kennen. Diese Information muss in den Logfiles der betroffenen Knoten geschrieben werden. Das
zwei Phasen Commit-Protokoll funktioniert für einen COMMIT-Befehl nun folgendermassen:
1.Phase Der Koordinator schickt allen beteiligten Datenbanken eine PREPARE TO COMMITMeldung. Diese Meldung bedeutet, dass alle beteiligten Datenbanken alle nötigen Logeinträge in ihrem lokalen Logfile schreiben müssen. Falls dies gelingt, wird OK an den
Koordinator geschickt, sonst NOK.
2.Phase Wenn der Koordinator die Antwort aller beteiligten Datenbanken erhalten hat, wird
er sich nun entscheiden, ob ein COMMIT oder ein ROLLBACK auszuführen ist. Sind
alle Antworten aus der 1.Phase OK, so wird ein COMMIT ausgeführt, sonst ein ROLLBACK. Diese Entscheidung wird im Logfile des Koordinators festgehalten. Nun wird jeder beteiligten Datenbank der Entscheid des Koordinators mitgeteilt und diese müssen
nun einen COMMIT-oder ROLLBACK-Befehl lokal ausführen. Anschliessend schickt
jedes System ein “ACK” (acknowledge) dem Koordinator. Wenn dieser alle Bestätigungen besitzt, kann er definitiv das Ende der Transaktion in seinem Logfile eintragen.
In der Abbildung 10-2 ist der Ablauf des 2 phasen Commit dargestellt.
Wir müssen uns noch damit befassen, was passiert wenn während diesem Ablauf ein System
oder ein Kommunikationsausfall stattfindet.
10.4.1
Koordinator Timeouts
Im Koordinator gibt es drei Zustände in denen ein Timeout passieren kann: WAIT, COMMIT
und ROLLBACK. Timeouts in den Zuständen COMMIT und ROLLBACK werden gleich
behandelt, so dass noch zwei Fälle übrigbleiben.
1. Timeout im WAIT-Zustand: In diesem Zustand wartet der Koordinator auf den Entscheid
der verschiedenen beteiligten Knoten. Der Koordinator entscheidet sich in diesem Fall
für ein Rollback. Er schreibt ein Rollback in das Logfile und sendet allen beteiligten eine
GLOBAL ROLLBACK Meldung.
2. Timeout im COMMIT- oder ROLLBACK-Zustand: In diesem Fall ist der Koordinator
nicht sicher, dass alle beteiligten Systeme die Transaction korrekt abgeschlossen haben
und kann daher die Transaktion nicht abschliessen. Der Koordinator hat in diesem Fall
keine andere Wahl, als allen Systemen die noch nicht geantwortet haben die Meldung
10-5
GLOBAL COMMIT oder GLOBAL ROLLBACK noch einmal zu senden und auf die
Antwort zu warten.
INITIAL
INITIAL
PREPARE
write
begin_commit
in log
No
write rollback
in log
VOTE ROLLBACK
Ready
to
commit?
Yes
VOTE COMMIT
WAIT
write ready
in log
Yes
Any No?
write rollback
in log
No
GLOBAL ROLLBACK
READY
GLOBAL COMMIT
Rollback
write commit
in log
ACK
ROLLBACK
write rollback
in log
ACK
COMMIT
write end
of Transaction
in log
ROLLBACK
Type
of
msg?
Commit
write commit
in log
COMMIT
Abbildung 10-2: 2 Phasen Commit
10.4.2
Timeout in den beteiligten Knoten
In einem beteiligten Knoten können Timeout im INITAL- und im READY-Zustand vorkommen.
1. Timeout im INITIAL-Zustand. In diesem Zustand wartet das System auf eine PREPAREMeldung. Wenn ein Timeout hier passiert, kann das System annehmen, dass der Koordinator abgestürtz ist oder dass die Kommunikation für längere Zeit ausgefallen ist. Der
Knoten kann in diesem Fall entscheiden, die Transaktion lokal zu beenden und schreibt
ein Rollback-Record ins Logfile. Für diesen Knoten ist diese Transaktion abgeschlossen.
Sollte später trotzdem eine PREPARE-Meldung eintreffen, so braucht dieser Knoten
nicht zu reagieren. Dies führt zu einem Timeout im WAIT-Zustand des Koordinators
und zu einem Globalen Rollback.
2. Timeout im READY-Zustand. In diesem Fall hat sich dieser Knoten für COMMIT
entschieden und dies dem Koordinator mitgeteilt. Diese Entscheidung kann er weder
10-6
rückgängig machen noch bestätigen, solange er nicht weiss, welche Globale Entscheidung
getroffen wurde. Das heisst, der Knoten muss dort warten, bis er den Entscheid vom
Koordinator oder von einem anderen beteiligten Subsystem erfährt.
10.4.3
Koordinator und Recovery
Wir wollen noch sehen was passiert, wenn der Koordinator während des 2 Phasen Commits
abstürzt und ein Recovery notwendig wird.
1. Der Koordinator stürzt im INITIAL-Zustand ab. Da bis dahin noch keine Kommunikation mit den anderen beteiligten Systemen stattfand, kann der Koordinator beim
Recovery einfach mit der COMMIT (ROLLBACK) Prozedur beginnen. Falls der begincommit-record vor dem Absturz ins Logfile geschrieben wurde, so muss dieser Schritt
natürlich nicht wiederhohlt werden.
2. Der Koordinator stürzt im WAIT-Zustand ab. In diesem Fall hat der Koordinator schon
die PREPARE-Meldung gesendet. Der Koordinator startet die COMMIT-Prozedur neu
und sendet allen beteiligten Knoten erneut eine PREPARE-Meldung.
3. Der Koordinator stürtz im COMMIT- oder ROLLBACK-Zustand ab. In diesem Fall
muss er entscheiden, ob er schon alle ACK-Meldungen erhalten hat oder nicht. Falls
nicht, sendet er den entsprechenden Knoten noch einmal die GLOBAL COMMIT oder
GLOBAL ROLLBACK Meldung.
10.4.4
Beteiligte Knoten und Recovery
In diesem Abschnitt wollen wir besprechen, wie das Recovery in den beteiligten Knoten funktionieren soll.
1. Ein Knoten im INITIAL-Zustand stürzt ab. Beim Recovery muss der Knoten die Transaktion mit ROLLBACK beenden. Diese Strategie ist richtig, da der Knoten nicht weiss,
ob alle Updates dieser Transaktion durchgeführt wurden. Wir können auch sehen, dass
dieses Verhalten zu keinen globalen Problemen führt. Wenn der Knoten abstürzt, so
ist der Koordinator im INITIAL- oder WAIT-Zustand. Da der beteiligte Knoten keine
Antwort gibt, wird nach einem Timeout der Koordinator eine GLOBAL ROLLBACK
Meldung an alle beteiligten Knoten senden.
2. Ein Knoten im WAIT-Zustand stürzt ab. Da der Prozess im Zustand WAIT ist, hat er
eine VOTE COMMIT-Meldung am Koordinator gesendet und kann diese Entscheidung
nicht mehr rückgängig machen. D.h. wie im Falle eines Timeouts muss er dort warten,
bis er die globale Entscheidung vom Koordinator oder von einem anderen beteiligten
System erfährt.
10.5
Isolation
Wir wollen als erstes betrachten, welche Probleme bei parallelen Transaktionen auftreten
können.
10-7
10.5.1
Verlorene Updates (lost Update)
Die Situation ist in der Abbildung 10-3 graphisch dargestellt.
Transaktion A
FETCH R
Zeit
t1
t2
UPDATE R
Transaktion B
FETCH R
t3
t4
UPDATE R
Abbildung 10-3: Update von Transaktion A wird zur Zeit t4 überschrieben
Die Transaktionen A und B ändern beide das Tupel R aufgrund der Daten, die sie bis zum
Zeitpunkt t2 gelesen haben. Zum Zeitpunkt t4 gehen die Änderungen, die von A zum Zeitpunkt t3 gemacht wurden, verloren.
10.5.2
Uncommitted Dependency
Die Situation ist in den Abbildungen 10-4 und 10-5 graphisch dargestellt.
Transaktion A
Zeit
UPDATE R
t1
t2
ROLLBACK
Transaktion B
FETCH R
t3
Abbildung 10-4: Probleme mit nicht abgeschlossenen Transaktionen
Im ersten Beispiel liest die Transaktion B zum Zeitpunkt t2, den von der Transaktion A zum
Zeitpunkt t1 modifizierten Tupel R ein. Da die Transaktion A noch keinen COMMIT-Befehl
ausgeführt hat, sagt man, dass Transaktion B von einem ’uncommitted update’ abhängig ist.
Ab dem Zeitpunkt t3 sind die von B eingelesenen Daten nicht mehr korrekt, da durch den
ROLLBACK-Befehl die Änderungen der Transaktion A rückgängig gemacht werden.
Im zweiten Beispiel ist die Situation noch schlimmer, da der Update der Transaktion B zum
Zeitpunkt t3 durch den ROLLBACK-Befehl der Transaktion A verloren geht.
10.5.3
Inkonsitente Analyse
Die Situation ist in der Abbildung 10-6 dargestellt. Transaktion A summiert die Werte in
den Konti Konto1 bis Konto3. In der Zwischenzeit wird von einer anderen Transaktion der
Betrag 10 von Konto3 zu Konto1 transferiert. A bekommt als Summe der 3 Konti 110 und
10-8
Transaktion A
Zeit
Transaktion B
t1
FETCH R
UPDATE R
t2
FETCH R
UPDATE R
t3
ROLLBACK
Abbildung 10-5: Verlust des Updates wegen einer nicht abgeschlossenen Transaktion
nicht 120. Wird diese Summe in der Datenbank zurückgeschrieben, so ist die Datenbank in
einem inkonsistenten Zustand.
Konto1
40
Transaktion A
FETCH Konto1 (40)
sum = 40
FETCH Konto2 (50)
sum = 90
FETCH Konto3 (20)
sum = 110 (nicht 120)
Konto2
50
Zeit
Konto3
30
Transaktion B
t1
t2
t3
FETCH Konto3 (30)
t4
UPDATE Konto3
20
30
t5
FETCH Konto1 (40)
t6
UPDATE Konto1
50
40
t7
COMMIT
t8
Abbildung 10-6: Transaktion A berechnet einen falschen Wert (inconsistent analysis)
10.5.4
Locking
Um die oben beschriebenen Probleme zu lösen, führt man Locking ein. Damit eine Transaktion
sicher ist, dass ein von ihr eingelesenes Tupel nicht von einer anderen Transaktion verändert
wird, lockt sie das entsprechende Tupel. Sobald dies getan ist, kann keine andere Transaktion
dasselbe Tupel verändern. Diesen Mechanismus wollen wir nun genauer betrachten:
1. Wir nehmen an, dass zwei Arten von Locks existieren. exklusive Locks (X-Locks) und
shared Locks (S-Locks).
2. Falls die Transaktion A das Tupel R mit einem X-Lock reserviert hat, so kann dieses
Tupel von keiner anderen Transaktion gelockt werden. Will die Transaktion B das Tupel
10-9
X
S
-
X
Nein
Nein
Ja
S
Nein
Ja
Ja
Ja
Ja
Ja
Abbildung 10-7: Kompatibilität von Locks
R locken, so muss sie warten bis die Transaktion A das Tupel wieder freigibt.
3. Falls die Transaktion A das Tupel R mit einem S-Lock reserviert hat, so kann eine
Transaktion B das Tupel mit einem S-Lock reservieren. Will die Transaktion B das
Tupel mit einem X-Lock reservieren, so muss sie warten, bis die Transaktion A das
Tupel wieder freigibt.
Die Punkte 1 bis 3 sind in der Kompatibilitätstabelle 10-7 zusammengefasst.
4. Die Locks auf Tupels werden normalerweise vom System implizit ausgeführt. Falls eine
Transaktion das Tupel R erfolgreich liest, so wird dieses automatisch mit einem SLock gesperrt. Falls eine Transaktion das Tupel R erfolgreich schreibt, so wird dieses
automatisch mit einem X-Lock gesperrt.
5. Am Ende einer Transaktion (COMMIT oder ROLLBACK) werden alle X-Locks und
S-Locks wieder freigegeben.
Wir wollen nun unsere drei Beispiele (lost Update, Uncommitted Dependency und inkonsistente Analyse) noch einmal betrachten und zwar mit Locking.
10.5.4.1
Verlorene Updates (lost Update)
Die Situation ist in der Abbildung 10-8 dargestellt.
Transaktion A
Zeit
FETCH R
S−Lock auf R
t1
t2
t3
UPDATE R
Warten auf X−Lock
Warten
t4
Warten
Warten
Warten
Warten
Deadlock
Transaktion B
FETCH R
S−Lock auf R
UPDATE R
Warten auf X−Lock
Warten
Warten
Abbildung 10-8: Lost Update Problem mit Locking
Der Update der Transaktion A wird zwar nicht mehr überschrieben, dafür entsteht ein Deadlock. Die Transaktion A wartet, dass die Transaktion B den S-Lock auf R freigibt und die
Transaktion B wartet, dass A den S-Lock auf R freigibt.
10-10
Transaktion A
Zeit
UPDATE R
X−Lock auf R
t1
t2
ROLLBACK
Release Locks
t3
t4
Transaktion B
FETCH R
Warten auf S−Lock
Warten
Warten
Warten
resume:FETCH R
S−Lock auf R
Abbildung 10-9: Transaktion B muss warten, bis Transaktion A beendet ist
Transaktion A
FETCH R
UPDATE R
X−Lock auf R
ROLLBACK
Release Locks
Zeit
Transaktion B
t1
t2
FETCH R
t3
Warten auf S−Lock
Warten
Warten
t4
resume:FETCH R
UPDATE R
X−Lock auf R
Abbildung 10-10: Transaktion B muss mit dem UPDATE warten, bis Transaktion A beendet
ist.
10.5.4.2
Uncommitted Dependency
Die Situation ist in den Abbildungen 10-9 und 10-10 graphisch dargestellt.
In diesem Fall entsteht kein Deadlock. Die Datenbank ist nach dem Ende der Transaktionen
A und B in einem konsistenten Zustand.
10.5.4.3
Inkonsitente Analyse
Die Situation ist in der Abbildung 10-11 dargestellt.
Auch hier löst der Lockingmechanismus das Problem der inkonsistenten Analyse, aber auch
wieder auf Kosten eines Deadlocks.
10.5.5
Behandlung der Deadlocks
Wir haben gesehen, dass die Probleme von parallelen Transaktionen mit Hilfe von Locks gelöst
werden können. Wir haben aber auch gesehen, dass dadurch Deadlocks entstehen können.
Deadlock ist eine Situation, in der zwei oder mehrere Transaktionen alle darauf warten, dass
die anderen Transaktionen Locks wieder freigeben.
Deadlock-Situationen werden vom DBMS erkannt. In diesem Fall wird das System eine der
Transaktionen, die für den Deadlock verantwortlich sind, auswählen und mit einem ROLLBACK beenden. Das Opfer dieser Operation wird vom DBMS benachrichtigt, damit es die
ganze Transaktion wiederhohlen kann.
10-11
Konto1
40
Konto2
Transaktion A
50
Zeit
Konto3
30
Transaktion B
FETCH Konto1 (40)
S−Lock auf Konto1
t1
sum = 40
FETCH Konto2 (50)
t2
S−Lock auf Konto2
sum = 90
t3
FETCH Konto3 (30)
S−Lock auf Konto3
t4
UPDATE Konto3
X−Lock auf Konto3
t5
20
30
FETCH Konto1 (40)
S−Lock auf Konto1
t6
FETCH Konto3 (20)
Warten auf S−Lock Konto3
Warten
Warten
t7
UPDATE Konto1
Warten auf X−Lock Konto1
Warten
Warten
Warten
Warten
Deadlock
Abbildung 10-11: Keine Inkonsistenz mehr aber, es entsteht ein Deadlock
10.6
Transaktionskontrolle ohne Locking
Lockmechanismen werden als pessimistische Verfahren der Transaktionskontrolle bezeichnet.
Man geht davon aus, dass immer ein Konflikt auftritt, wenn er nicht durch ein Lock verhindert wird. Dieses Verfahren hat den Nachteil, dass für die Verwaltung der Locks sehr viel
Systemresourcen notwendig sind. Auf verteilten Systemen wird der Verwaltungsaufwand noch
viel grösser (senden von Messages zum Setzen und Testen von Locks).
10.6.1
Optimistische Transaktionskontrolle
Die optimitische Transaktionskontrolle geht davon aus, dass Konflikte nur sehr selten auftreten.
In diesem Fall wird eine Transaktion vollständig ohne Sperren ausgeführt. Erst wenn ein
COMMIT-Befehl kommt, wird getestet, ob keine Konfliktsituation aufgetreten ist (kann anhand des Logfiles entschieden werden). Wird eine Konfliktsituation entdeckt, so kann die ganze
Transaktion neu gestartet werden. In solchen Systemen werden keine Update-Operationen auf
die Datenbank zurückgeschrieben, bevor die Transaktion erfolgreich abgeschlossen ist. Muss
eine Transaktion wegen eines Konfliktes neu gestartet werden, so müssen keine Änderungen
rückgängig gemacht werden.
10.6.2
Zeitstempel (Timestamp) Methode
Auch die Zeitstempel Methode geht davon aus, dass Konflikte nur sehr selten auftreten. Die
Idee ist, dass wenn eine Transaction A vor einer Transaction B gestartet wird, das System
sich so verhalten sollte, wie wenn die Transaktion A vollständig ausgeführt wird, bevor die
Transaktion B startet (virtuelle Serialisierung). Das heisst, A darf nie ein Tupel einlesen, dass
durch B verändert wird und nie ein Tupel schreiben, dass von B schon eingelesen wurde. Dies
kann mit Hilfe von Zeitstempeln erreicht werden.
• Beim Start bekommt jede Transaktion einen Zeitstempel t.
• Will eine Transaktion A ein Tupel lesen, so wird der Zeitstempel tA der Transaktion
A mit dem Zeitstempel tX der letzten Transaktion X, die das Tupel verändert hat,
verglichen. Ist tX grösser als tA , so haben wir einen Konflikt und die Transaktion A
muss abgebrochen und neu gestartet werden.
10-12
• Will eine Transaktion A ein Tupel schreiben, so wird der Zeitstempel tA der Transaktion A mit dem Zeitstempel tX der letzten Transaktion X, die das Tupel gelesen (oder
verändert) hat, verglichen. Ist tX grösser als tA, so haben wir einen Konflikt und die
Transaktion A muss abgebrochen und neu gestartet werden.
10.7
Transactionen und SQL
Wir wollen am Ende des Kapitels noch sehen wie die Transaktionen mit Hilfe von SQL gesteuert werden können.
10.7.1
Sart einer Transaktion
In SQL werden die Transactionen implizit gestartet. Wenn ein sogenanntes “transactioninitiating” SQL-Befehl ausgeführt wird und noch keine Transaktion aktiv ist, so wird eine
neue Transaktion gestartet (Transaktionen können also nicht geschachtelt werden). Die Optionen einer Transaction können mit dem “SET TRANSACTION” Befehl vor der Transaktion
gesetzt werden (siehe weiter unten).
Bemerkung 10.3 [START TRANSACTION] Im SQL-Standard 2003 können Transaktionen explizit mit dem “START TRANSACTION” Befehl initiert werden. Mit diesem Befehl
können gleichzeitig die Optionen der Transaktion gesetzt werden. Auch mit diesem Befehl
können keine geschachtelten Transaktionen gestartet werden. Der Befehl “START TRANSACTION” generiert einen Fehler, wenn er während einer aktiven Transaktion aufgerufen wird.
In der nachfolgenden Liste sind die Statements aufgeführt, die keine neue Transaktionen
initialisieren. Alle anderen Befehlen starten eine neue Transaktion, falls noch keine aktiv ist.
CONNECT
DISCONNECT
COMMIT
ROLLBACK
DECLARE CURSOR
DECLARE LOCAL TEMPORARY TABLE
BEGIN DECLARE SECTION
END DECLARE SECTION
WHENEVER
alle SET Befehle
alle GET Befehle
10.7.2
Ende einer Transaktion
Jede Transaktion wird entweder mit einem “COMMIT” oder “ROLLBACK” Befehl beendet.
Änderungen, die von einer Transaktion T1 vorgenommen werden sind für eine andere Transaktion T2 unsichtbar bis die Transaktion T1 einen COMMIT-Befehl ausführt.
Ein COMMIT-Befehl löst die folgenden Aktionen aus:
1. Auf allen offenen Cursor wird ein impliziter CLOSE-Befehl ausgeführt.
2. Alle auf das Ende der Transaktion verschobenen Integritätstests werden ausgeführt.
10-13
3. Alle in der Transaction getätigten Änderungen werden permanent gemacht.
Ein ROLLBACK-Befehl löst die folgenden Aktionen aus:
1. Auf allen offenen Cursor wird ein impliziter CLOSE-Befehl ausgeführt.
2. Alle in der Transaction getätigten Änderungen werden rückgängig gemacht und die
Transaktion terminiert mit einem Fehler.
Bemerkung 10.4 [Savepoints] Im SQL-Standard 2003 sind auch sogenannte geschachtelte
Transaktionen zugelassen. Die Transaktion kann mit Hilfe von SAVEPOINTS in Subtransaktionen unterteilt werden. Somit hat man die Möglichkeit mit einem ROLLBACK Befehl bis zu
einem gegebenen SAVEPOINT zurückzuspringen. Die Umgebende Transaktion bleibt dabei
offen und muss ganz am Schluss normal mit einem COMMIT oder ROLLBACK abgeschlossen
werden.
10.7.3
Eigenschaften einer Transaktion
Mit dem SET TRANSAKTION Befehl hat man die Möglichkeit gewisse Eigenschaften einer Transaktion zu beeinflussen. Dieses Statement kann nur ausgeführt werden, wenn keine
Transaktion aktiv ist. Die Eigenschaften, die gesetzt werden können sind der “access mode”
und der “isolation level”.
Die Syntax des Befehls lautet:
transactionoption::= SET TRANSACTION [accessmode]
[,isolationlevel]
accessmode
::= READ ONLY | READ WRITE
isolationlevel
::= SERIALIZABLE | REAPEATABLE READ |
READ COMMITTED | READ UNCOMITTED
10.7.3.1
“access mode”
Die beiden Optionen, die angegeben werden können sind READ ONLY oder READ WRITE.
Falls kein “access mode” angegeben ist, so gilt READ WRITE. Wie der Name ja sagt können
während einer READ ONLY Transaktion keine Updates auf der Datenbank vorgenommen
werden. Wenn der isolation level READ UNCOMMITTED ist, so muss der access mode
READ ONLY sein.
10.7.3.2
“isolation level”
Wie es der Name sagt geht es bei dieser Eigenschaft um den Grad der Isolation einer Transaktion. Der höchste Grad ist Serialisierbarkeit. Falls alle Transaktionen auf diesem Grad arbeiten, so ist garantiert, dass das Resultat von concurrent Transaktionen dasselbe Resultat
liefert, wie eine beliebige seriele Abarbeitung dieser Transaktionen (man spricht dann vom
ACIDS Prinzip). Der Default für den isolation level ist SERIALIZABLE. Falls ein anderer
level gesetzt wird, so darf die Datenbank auch einen höheren level setzen in dieser Reihenfolge:
10-14
READ UNCOMMITTED < READ COMMITTED < REAPEATABLE READ < SERIALIZABLE
In Datenbanken sind die folgenden Verletzungen der Serialisierbarkeit (je nach isolation level)
möglich.
1. Dirty read: Dies ist dasselbe wie “uncommitted dependency” (siehe 10.5). Die Transaktion T1 macht einen Update auf ein Datenobjekt. Die Transaktion T2 liest das Datenobjekt T1 führt ein ROLLBACK-Befehl aus. Nun hat T2 einen “verschmutzten” Wert
gelesen.
2. Nonrepeatable read: Wir nehemen an, dass T1 einen Wert einliest. T2 verändert nun
diesen Wert. Jetzt liest T1 dasselbe Datenobjekt noch einmal und sieht einen anderen
Wert. Dies entspricht dem Problem der inkonsistenten Analyse (siehe 10.5).
3. Phantoms: Wir nehmen an, dass die Transaktion T1 eine Menge von Tupeln nach einem
gewissen Kriterium selektiert. Nun kommt Transaktion T2 und fügt ein neues Tupel ein,
dass dem Selektionskriterium genügt. Falls Transaktion T1 nun die Selektion erneut
durchführt hat es nun ein Tuple das vorher nicht existierte (ein “Phantom”).
Die von SQL definierten “isolation level” geben nun an, welche der obigen Verletzungen der
Serialisierbarkeit möglich sind und welche nicht. In der folgenden Tabelle sind die möglichen
Werte für den “isloation level” angegeben.
isolation level
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
dirty read
ja
nein
nein
nein
10-15
nonrepeatable read
ja
ja
nein
nein
phantom
ja
ja
ja
nein
10.8
Übungen
10.8.1
Theoretische Fragen
Aufgabe 10.1 [Two phase commit] Wir nehmen an, dass bei einem “two-phase commit”
der Master zwischen der ersten und der zweiten Phase abstürzt. Beschreiben Sie was der
Master nach dem Restart unternehmen muss, um die verteilte Datenbank wieder in einem
konsistenten Zustand zu bringen.
Aufgabe 10.2 [Locking] Nachfolgend sind drei Transaktionen (schematisch) beschrieben,
die alle dasselbe Datenbankobjekt A verändern. Die Transaktionen können in einer beliebiegen
Reihenfolge stattfinden (auch “gleichzeitig”).
T1
F1:
U1:
Fetch A into t1;
t1 = t1 + 1;
Update Set A = t1;
T2
F2:
U2:
Fetch A into t2;
t2 = 2 * t2;
Update Set A = t2;
T3
F3:
U3:
Fetch A into t3;
display t3;
Update Set A = 7;
Am Anfang sei der Wert von A gleich null.
1. Welchen Wert kann A nach dem Ablauf der 3 Transaktionen haben, wenn das System
kein Locking zur Verfügung stellt?
2. Welchen Wert kann A nach dem Ablauf der 3 Transaktionen haben, wenn das System
mit S-Locks und X-Locks arbeitet?
3. Welchen Wert kann A nach dem Ablauf der 3 Transaktionen haben, wenn das System
nur mit X-Locks arbeitet?
Aufgabe 10.3 [Serialiaierbar] Konstruieren Sie zwei Transaktionen, die nicht in jedem
Fall serialisierbar sind. Geben Sie an, in welchem Fall Ihre Transaktionen nicht serializierbar
sind. Was sollte Ihrer Meinung nach die Datenbank in einem solchen Fall tun?
10.8.2
Praktische Übungen
Aufgabe 10.4 [Experimente] Um die folgenden Experimente durchzuführen müssen Sie das
Programm dbframe zweimal starten. In beiden Fenstern müssen Sie die Commit Strategy
auf Commit by user einstellen.
10-16
a) Versuchen Sie in beiden Fenstern die gleiche Person einzulesen und anschliessend in
einem Fenster zu verändern (Lost Update Problem).
b) Stellen Sie ein Experiment an um festzustellen, ob die Datenbank Mysql Tuples oder
ganze Datenbankseiten lockt. Was hat diese Tatsache für Konsequenzen?
c) Versuchen Sie einen Daedlock zu produzieren und kontrollieren Sie ob Mysql diesen
Zustand richtig erkennt
d) Testen Sie die verschiedenen ’Isolation level’ und beschreiben Sie wie Mysql die verschiedenen Levels implementiert.
10-17
10-18
Kapitel 11
Object Persistenz in Java
In diesem Kapitel geht es um die Möglichkeit, Objekte der Sprache Java persistent zu machen.
Das heisst, es besteht die Möglichkeit die Objekte in einer relationalen Datenbank zu speichern. Wir kennen aus dem Kapitel “SQL in Programmiersprachen” (9) schon eine Technik (Jdbc) um mit einer Datenbank zu kommunizieren und Daten zu speichern und wieder zu finden.
Bei dieser Technik ist aber das Problem, dass Objekte aus der Programmiersprache und Tupel
in Relationen der Datenbank nicht zusammenpassen. Man nennt dieses Problem oft “Objectrelational Impedance Mismatch”. Mit Jdbc müssen die Daten aus einem Tupel extrahiert
werden und in ein Objekt transferiert werden oder umgekehrt. In diesem Kapitel wollen wir
eine Möglichkeit betrachten, dieses Problem mit hilfe von Object-Relational-Mapping (kurz
O/R-mapping) zu lösen.
Wir werden dafür das Java Persistence API (kurz JPA 2.0) verwenden, das im JSR317 (Java
Specification Request) spezifiziert ist. Dieses Dokument erhebt keinen Anspruch auf Vollständigkeit (die JPA-Spezifikation hat über 400 Seiten) sondern ist nur eine Einführung in
wichtige Konzepte von JPA. Weitere Informationen sind in der Spezifikation JSR317 vom
August 2009 zu finden.
Bemerkung 11.1 [Java EE und Java SE] JPA ist sowohl für Java EE wie auch für Java
SE spezifiziert. In diesem Abschnitt wird nur auf Java SE eingegangen.
11.1
Persistenz
In diesem Kapitel befassen wir uns mit Objektpersistenz. Persistenz kann als Abstraktion des
Speichers angesehen werden, indem wir die krassen Unterschiede zwischen Hauptspeicher und
externem Speicher aufheben und nur definieren, wie lange wir ein Objekt behalten wollen.
Wie und wo dieses Objekt gespeichert wird ist nicht ein Teil der Applikation, sondern wird
extern mit Hilfe einer XML-Definition oder mit Hilfe von Annotationen realisiert. Das Objekt
kann dabei in einer relationalen, einer objektrelationalen, einer objektorientierten oder einer
XML-Datenbank abgelegt werden, ohne dass dabei die Applikation verändert werden muss.
11.1.1
Definition der Persistenz
Nachfolgend ist eine Definition der Persistenz nach Atkinson.
Definition 11.1 [Persistenz] Persistenz ist die Fähigkeit der Daten (Objekte),
beliebige Lebensdauern anzunehmen, wobei zwei Prinzipien eingehalten werden
müssen.
11-1
• Typ-Orthogonalität: Daten beliebiger (auch komplexer) Typen sollen persistent gemacht werden können.
• Unabhängigkeit der Programme von der Persistenz: Programme sollen unverändert bleiben, wenn die Daten ihre Lebensdauer ändern.
Das zweite Prinzip bedeutet, dass Persistenz implizit in der Sprache enthalten sein soll. Persistente Objekte müssen also gleich behandelt werden wie transiente Objekte, deren Lebensdauer
am Ende eines Blocks, einer Prozedur oder eines Programms endet. Wir können sagen:
Wenn wir bei bestimmten Objekten die Lebensdauer verlängert haben, so sollen
keine Move- oder Copy-Befehle nötig sein, um die Persistenz zu verwirklichen. Der
Transport der Daten zwischen dem Hauptspeicher und dem externen Speicher ist
implizit und muss durch das System garantiert werden.
11.1.2
Techniken zur Realisierung der Persistenz
Die Persistenz in einem O/R-System kann prinzipiell auf zwei verschiedene Arten eingeführt
werden:
1. Klassenabhängige Persistenz:
Bei dieser Variante kann eine Klasse als persistent definiert werden. Alle Objekte dieser
Klasse sind dann automatisch persistent. Dies ist der gleiche Ansatz wie in einer relationalen Datenbank, wo alle Tupel einer Relation automatisch persistent sind.
2. Objektabhängige Persistenz:
In Java steht eine Metthode zur Verfügung, die es erlaubt ein normal erzeugtes Objekt
einer Klasse persistent zu machen. Damit können persistente und transiente Objekte
einer Klasse gemischt werden. Nach Ende des Programms existieren nur noch die persistenten Objekte.
Da persistente Objekte als Komponentenobjekte persistente oder transiente Objekte haben
können, muss noch definiert werden, wie sich die Persistenz über einen solchen Objektgraphen
fortpflanzt.
1. Explizite Persistenz:
Jedes Objekt, das persistent sein soll, muss einer persistenten Klasse angehören oder
als persistent erzeugt worden sein. Transiente Komponentenojekte eines persistenten
Objekts gehen also nach Ende des Programms verloren.
2. Persistenz durch Erreichbarkeit:
Jedes Objekt, das in einem Objektgraphen von einem anderen, persistenten Objekt aus
erreicht werden kann, ist auch persistent.
Wir werden sehen, dass JPA beide Möglichkeiten zur Verfügung stellt.
11-2
11.1.3
Objektrelationales Mapping
Eine Möglichkeit Persistenz in einer objektorientierten Sprache einzuführen ist die Technik
des objetrelationalen Mappings. In der Programmiersprache werden hauptsächlich Objekte
manipuliert, die in den meisten Fällen nicht skalar sind, sondern sogenannte komplexe Objekte
die andere Objekte und Mengen von Objekten als Komponenten haben können. In einer
relationalen Datenbank kennt man aber nur die Relationen, die aus flachen Tupeln bestehen,
das heisst, ein Attribut kann nur atomare Werte enthalten. Ein weiteres Problem ist die
Vererbung, die zum objektorientierten Paradigma gehört aber nicht zum relationalen.
Ein Ansatz um das Problem zu Lösen, haben wir im Abschnitt “ERD und Datenbankschema”
7.2 schon besprochen. Dort haben wir gezeigt, wie ein ER-Modell in ein relationales Datenbankschema übersetzt werden kann. Da ein ER-Modell ähnlich wie ein Klassendiagramm
aufgebaut ist, können wir in etwa dieselben Methoden brauchen, um ein Klassendiagramm
auf ein relationales Datenbankschema abzubilden. Wir werden sehen, dass JPA es uns erlaubt genau diese Abbildung zu definieren. Wenn das geschehen ist kann man anschliessend
tatsächlich in Java persistente Objekte wie transiente Objekte behandlen.
Wir müssen die folgenden Probleme mit Hilfe des O/R-Mappings lösen können:
1. Abbilden der einzelnen java Klassen.
2. Definieren einer Objektidentität, die für die ganze Lebensdauer eines Objekts gültig ist.
3. Abbilden aller primitiven Datentypen von Java in die Datenbank.
4. Abbilden von Collections von primitiven Datentypen (Attributte mit Kardinalität M).
5. Abbilden der Vererbungshierarchie
6. Abbilden der Beziehungen zwischen den Objekten mit allen möglichen Kardinalitäten.
7. Suchen von persistenten Objekten mit Hilfe einer Querysprache.
11.2
Java Persistence API (JPA)
Java Persistence API (im folgenden JPA) ist wie schon gesagt der neue O/R-Mapping Standard für Java. Es gibt schon jetzt viele Implementationen des Standards. Unter anderem
Hibernate, TopLink, EclipseLink, OpenJpa usw. Jede Implementation bietet neben dem Standard noch eigene Erweiterungen. Alle Beispiele in diesem Skript sind mit OpenJPA2.0 von
apache getestet.
11.2.1
Vorgehen
Als nächstes wollen wir beschreiben, wie ein System mit persitenten Objekten aufgebaut wird.
Dabei kann man zwei Fälle unterscheiden:
1. Die Datenbank existiert.
• Aus dem Aufbau der Datenbank müssen die einzelnen Objekte und die Beziehungen
der Objekte zueinander identifiziert werden. Aus diesem Schritt erhalten wir ein
Klassendiagramm. Falls die Datenbank sauber mit Hilfe eines ER-Modells aufgebaut wurde, so dürfte dieser Schritt leicht sein.
11-3
• Im nächsten Schritt werden die Klassen ganz normal in Java implementiert.
• Nun muss das objektrelationale Mapping mit Hilfe von Annotations oder eines
XML-Metadata Files definiert werden. Dabei ist darauf zu achten, dass das Mapping mit der bestehenden Datenbank kompatible bleibt.
• Nun kann mit den definierten Klassen normal gearbeitet werden und die entsprechenden Objekte können nun auch persistent gemacht werden.
2. Es existiert für das System noch keine Datenbank
• In diesem Fall kann man mit dem Design eines Klassendiagramms beginnen.
• Im nächsten Schritt werden die Klassen ganz normal in Java implementiert.
• Nachdem ein Klassendiagramm und die Klassen zur Verfügung stehen kann das objektrelationale Mapping definiert werden und die Datenbank entsprechend kreiert
werden. Es ist darauf zu achten, dass der Aufbau der Datenbank die Regeln für
relationale Datenbanken befolgt (Normalisierung usw.).
• Nun kann mit den definierten Klassen normal gearbeitet werden und die entsprechenden Objekte können nun auch persistent gemacht werden.
Bemerkung 11.2 [Mapping Tools] In jeder Implementation existieren sogenannte mapping Tools, mit denen aus einem bestehenden Datenbankschema die entsprechenden Klassen
und die Mappinginformation automatisch generiert werden können. Es ist auch möglich aus
bestehenden Klassen die Mappinginformation und die entsprechende Datenbank zu generieren.
11.2.2
Mapping mit JPA
Wir wollen nun an Hand von Beispielen zeigen, wie das Mapping in JPA mit Hilfe von Annotationen definiert werden kann. Diese Ausführungen sind nicht vollständig. Für weitere mögliche
Annotationen sei auf den Standard verwiesen.
Zum illustrieren des Mappings verwenden wir das in der Abbildung 11-1 angegebene Klassendiagramm. Es enthält alle Konstrukte, die wir im folgenden illustrieren wollen.
11.2.2.1
Entity
Die Annotation @Entity dient dazu eine Klasse als persistent zu definieren. Falls die Tabelle
in der Datenbank nicht denselben Namen hat wie die Klasse so kann der Tabellenname mit
Hilfe der Annotation @Table definiert werden.
Beispiel 11.1 [Entity]
// Die Klasse ist persistent
@Entity
// Der name der entsprechenden Tabelle in der Datenbank ist "personentabelle"
@Table(name="personentabelle")
public class Person {
.
.
}
Damit eine Javaklasse als Entity deklariert werden kann müssen die folgenden Bedingungen
erfüllt sein:
11-4
<<entity>>
Person
pNr
name
vorname
geburtsdatum
text
hobbies[0..*]
alter
<<entity>>
Ausleihe
1
hat
0..* aId
ausleihdaDatum
1
betrifft
1
<<entity>>
Publikation
0..*
referenziert
0..*
<<entity>>
Buch
autor
puNr
titel
<<entity>>
Zeitschrift
jahr
ausgabe
artikel[0..*]
Abbildung 11-1: Beispiel Personen und Publikationen
• Die Klasse muss einen parameterlosen public oder protected Konstruktor besitzen.
• Die Klasse darf nicht final sein und keine final Methoden enthalten.
• Die Klasse muss ein oder mehrere Attribute als Identität eines Objektes deklarieren
(Primärschlüssel).
• Die Klassen sollten ein Versionsattribut deklarieren, das benutzt wird um gleichzeitige
Änderungen am gleichen Objekt zu detektieren. Dieses Attribut ist nicht obligatorisch
aber wenn es fehlt ist es möglich, dass zwei Prozesse wiedersprüchliche Änderungen am
gleichen Objekt vornehmen. Mit dem Versionsattribut wird ein sogenannter “optimistic
locking” Mechanismus realisiert. Der Datentyp des Attributs muss cardinal sein (int,
long usw.) oder Timestamp.
Bemerkung 11.3 [Pesimistic Locking] Ab Jpa 2.0 ist auch “pesimistic locking” vorgesehen. Das bedeutet, dass ein Objekt vor dem schreiben gelockt wird um zu verhindern, dass
andere Prozesse auf dieses Objekt zugreiffen können. Der Lock wird am Ende der Transaktion
freigegeben. Welche Art des Lockings verwendet wird (“optimistic” oder “pesimistic”) hängt
stark von der gegebenen Applikation ab.
11.2.2.2
Identität (ein Attribut)
In Java hat jedes Objekt eine (transiente) Objektidentität, die während der Lebensdauer eines
Objektes in der Java virtual machine nicht ändert. Die Objektidentität von Java ist in JPA
nicht brauchbar, da diese nach jedem neuen Laden eines Objektes im Speicher verschieden
ist. Daher führt JDA eine eigene Objektidentität ein, die das Objekt während seiner ganzen
Lebensdauer behält. Dazu dient die Annotation @Id, die angibt, welches Attribut der Klasse
als Identität (Primärschlüssel) fungieren soll.
Die Identität eines Objektes kann mit der Annotation @GenerateValues auch automatisch
generiert werden. Dazu können verschiedene Strategien angegeben werden (ist von den Möglichkeiten der Datenbank abhängig).
11-5
Beispiel 11.2 [Identity]
// Die Klasse ist persistent
@Entity
// Der name der entsprechenden Tabelle in der Datenbank ist "personentabelle"
@Table(name="personentabelle")
public class Person {
// Das Attribut pNr ist der Primärschlüssel
@Id
// Der Schlüssel wird automatisch generiert
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int pNr;
@Version
// Versionsfeld fuer optimistic locking
private long version
}
11.2.2.3
Identität (mehrere Attribute)
Falls der Primärschlüssel aus mehr als einem Attribut besteht, so muss eine sogenannte Id
Klasse implementiert werden, welche diesen Schlüssel repräsentiert. Anschliessend kann diese
Klasse mit der Annotation @IdClass deklariert werden. Diese Situation kommt meistens dann
vor, wenn die Datenbank vorher schon existierte. In diesem Fall ist es nicht selten, dass die
Schlüssel zusammengesetzt sind.
Die Id-Klasse muss serialisierbar sein, einen parameterlosen Konstruktor besitzen und muss
sowohl die Methode equals wie hashCode überschreiben.
Beispiel 11.3 [Identity (mehere Attribute]
In diesem Beispiel besteht der Schlüssel der Klasse Magazine aus den beiden Attributen isbn
und titel. Beide Attribute werden mit der Annotation @Id markiert. Die Id-Klasse MagazineId.
wird mit der Annotation @Idclass angegeben.
// Die folgende Klasse hat einen zusammengesetzten Schluessel
@IdClass(Magazine.class)
@Entity
public class Magazine {
@Id
private String isbn;
// Schluessel Attribut
@Id
private String titel;
// Schluessel Attribut
}
// Objekte der folgenden Klasse sind Schluessel der Klasse Magazine
public class MagazineId implements Serializable {
// Klasse muss alle Attribute des Schluessels enthalten. Name und
// Datentyp muessen gleich sein.
public String isbn;
public String titel;
//Klasse muss einen parameterlosen Konstuktor haben
public MagazineId() {}
//equals Methode muss imlementiert werden.
public boolean equals(Object other) {
}
//hashCode muss auch implementiert werden
public int hashCode() {
}
}
11-6
11.2.2.4
Attribute
Die Attribute einer Klasse können auf zwei Arten als persistent definiert werden und zwar
entweder “field access”, falls die Annotation bei der Deklaration des Attributs geschrieben wird
oder “property access”, falls die Annotation bei der Gettermethode eines Attributs geschrieben
wird. Bei “property access” wird der Datenverkehr mit der Datenbank über die Getter- und
Seter-Methoden geregelt.
Nicht annotierte Attribute sind per Default persistent. Will man, dass ein Attribut nicht in
die Datenbank gespeichert wird, so muss das Attribut explizit mit der Annotation @Transient
markiert werden.
Normale Attribute haben die Annotation @Basic (dies ist der Default). Die Annotation @Temporal wird beim Datentyp Calendar oder Date verwendet um anzugeben, welchen Teil des
Datums in der Datenbank gespeichert werden soll (Date, Timestamp oder Time). Für sehr
lange Felder (CLOB oder BLOB) steht die Annotation @Lob zur Verfügung.
Mit der Annotation @Column kann die Speicherung des Attributs in der Datenbank gesteuert
werden (name des Attributs, nullable, length usw.).
Beispiel 11.4 [Attributes]
// Die Klasse ist persistent
@Entity
// Der name der entsprechenden Tabelle in der Datenbank ist "personentabelle"
@Table(name="personentabelle")
public class Person {
// Das Attribut pNr ist der Primärschlüssel
@Id
// Der Schlüssel wird automatisch generiert
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int pNr;
@Version
// Versionsfeld fuer optimistic locking
private long version
@Basic
// Die Annotation dient dazu, das Attribut in der Datenbank zu definieren.
@Column(name="familienname", length=30, nullable=false)
private String name;
// Normales Attribut (in der DB werden Defaults angenommen)
@Basic
private String vorname;
// In diesem Fall wird nur ein Datum gespeichert. Möglich wären auch TIMESTAMP und TIME
@Temporal(TemporalType.DATE)
private Calendar geburtsDatum;
// Dieser Text ist ein langer Text (CLOB)
@Lob
private String text;
// Transientes Attribut wird nicht in der Datenbank gespeichert.
@Transient
private int age;
public Person() {
}
}
11-7
11.2.2.5
Mengenwertige Attribute
Für mengenwertige Attribute steht die Annotation @ElementCollection zur Verfügung. Damit
kann ein Attribut auch beliebige Kardinalität aufweisen. Der Name der Hilstabelle, des Fremdschlüssels und des Datenelements in der Hilfstabelle können mit Hilfe der Annotationen @CollectionTable und @Column gewählt werden.
Beispiel 11.5 [Multiple Attributes] Dieses Beispiel benutzt die Annotation @ElementCollection um ein Attribut mit Kardinalität M zu realisieren.
// Die Klasse ist persistent
@Entity
// Der name der entsprechenden Tabelle in der Datenbank ist "personentabelle"
@Table(name="personentabelle")
public class Person {
.
.
// Eine Menge von Strings (Kardinalität M)
@ElementCollection
// Diese Annotation dient dazu die Namen der Hilfstabelle und des Fremdschluessels zu definieren
@CollectionTable(name="personenhobby",
joinColumns=@JoinColumn(name="pNr"))
// Diese Annotation dient dazu, den Namen des Datenfeldes in der Hilstabelle zu definieren
@Column(name="hobby")
private Collection<String> hobbies = new HashSet<String>();
.
}
11.2.2.6
Datenbanktabellen zur Klasse Person
Wir wollen nun sehen, wie die Datenbanktabellen zu unserem bisherigen Beispiel der Klasse
Person aussehen. Die SQL-Definitionen sind für die Datenbank Mysql angegeben.
CREATE TABLE personentabelle (
pNr
int(11) NOT NULL auto_increment,
version
int(11) default NULL,
familienname
vorname
geburtsDatum
freiertext
varchar(30) NOT NULL,
varchar(30) default NULL,
date default NULL,
text,
PRIMARY KEY (pNr)
) ENGINE=InnoDB;
CREATE TABLE hobbytabelle (
pNr
int(11) default NULL,
hobby
varchar(255) default NULL,
PRIMARY KEY (pNr, hobby),
FOREIGN KEY (pNr) references personentabelle (pNr)
ON DELETE CASCADE
) ENGINE=InnoDB;
Sobald diese Tabellen in der Datenbank kreiert sind können wir die Objekte der Klasse Person
auch als persistente Objekte benutzen.
11.2.2.7
Vererbungshierarchie
Im Abschnitt 7.2.2 haben wir gesehen, dass man in einer Datenbank die Spezialiesierung
entweder vertikal oder horizontal realisieren kann. Beide Varianten sind auch mit jpa möglich.
Wir wollen an dieser Stelle aber nur die vertikale Variante betrachten.
11-8
Als Beispiel wollen wir die Klasse Publikation mit den beiden Subklassen Buch und Zeitschrift
betrachten, wobei Publikation eine abstrakte Klasse ist. Die Strategie für die Abbildung der
Hierarchie kann mit der Annotation @Inheritance angegeben werden.
Beispiel 11.6 [Vererbung] Nachstehend die Klassen für das Beispiel in der Abbildung 11-1.
@Entity
@Table(name="publikation")
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Publikation {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int puNr;
@Version
private long version
private String titel;
}
@Entity
@Table(name="buch")
public class Buch extends Publikation{
private String autor;
}
@Entity
@Table(name="zeitschrift")
public class Zeitschrift extends Publikation {
private int jahr;
private int ausgabe;
@ElementCollection(fetch=FetchType.LAZY)
@CollectionTable(name="artikelliste",
joinColumns=@JoinColumn(name="puNr"))
@Column(name = "title")
private Collection<String> artikel = new HashSet<String>();
}
Hier noch die SQL Definitionen für dieses Beispiel. Die Definition ist analog zum Kapitel 7.2.2
CREATE TABLE publikation (
puNr
int(11) NOT NULL auto_increment,
version
int(11) default NULL,
titel
aid
varchar(30) NOT NULL,
int(11) default NULL,
PRIMARY KEY (puNr),
FOREIGN KEY (aid) references ausleihe (aId),
UNIQUE KEY (aid)
) ENGINE=InnoDB;
CREATE TABLE buch (
puNr
int(11) NOT NULL,
autor
varchar(30) NOT NULL,
PRIMARY KEY (puNr),
FOREIGN KEY (puNr) references publikation (puNr)
) ENGINE=InnoDB;
CREATE TABLE zeitschrift (
puNr
int(11) NOT NULL,
jahr
int(11) NOT NULL,
ausgabe
int(11) NOT NULL,
PRIMARY KEY (puNr),
FOREIGN KEY (puNr) references publikation (puNr)
) ENGINE=InnoDB;
11-9
11.2.2.8
Beziehungen
Als letztes wollen wir noch zeigen, wie die Beziehungen zwischen Entitäten mit Hilfe von jpa
dargestellt werden können. Grundsätzlich kann dies mit Collections in den entsprechenden
Klassen realisiert werden. Im folgenden wird gezeigt, wie 1:1, 1:M und M:M Beziehungen
realisiert werden. Dazu benutzen wir wieder das Beispiel in der Abbildung 11-1.
Wir wollen in diesem Skript nur bidirektionale Beziehungen betrachten. Dazu noch wichtige
Hinweise:
1. Eine Beziehung gehört (“is owned by”) immer zu einer der beiden partizipierenden
Klassen. Die Besitzerin der Beziehung ist dafür verantwortlich, dass die entsprechende
Beziehung in der Datenbank nachgeführt werden.
2. Mit dem Attribut “mappedBy” der Beziehungsannotation wird angegeben, welche Seite
der Beziehung diese besitzt. Im Fall 1:M meistens die M Seite (dort wo der Fremdschlüssel eingetragen ist).
3. Falls eine neue Beziehung eingefügt (oder gelöscht) wird, so muss das Programm dies auf
beiden Seiten der Beziehung tun. Falls dies nicht gemacht wird, ist die Beziehung bis die
Objekte wieder von der Datenbank eingelesen werden nicht konsistent. Vor allem muss
darauf geachtete werden, dass die Änderungen auf der besitzenden Seite der Beziehung
nachgeführt werden, damit diese auch in der Datenbank reflektiert werden.
4. Das Attribut “cascade” der Beziehungsannotation steuert das verhalten des Systems
bei den operationen “insert”, “update” und delete. Es gibt an, welche Operationen, die
auf ein Objekt ausgeführt werden, auf die durch die Beziehung referenzierten Objekte
wirken. Das Attribut kann ein oder mehrere der folgenden Werte haben.
PERSIST
REMOVE
REFRESH
MERGE
ALL
Wird das Objekt persistent gemacht, so wird diese Operation auf
die referenzierten Objekte kaskadiert
Wird das Objekt gelöscht, so werden die referenzierten Objekte
auch gelöscht
Falls das Objekt mit der Datenbank neu synchronisiert wird, so
gilt dies auch für die referenzierten Objekte
Die “merge” Operation (siehe 11.2.4) wird auch auf die referenzierten Objekte ausgeführt.
Alle obigen Optionen werden gesetzt.
1:1 Beziehung
Mit der Annotation @OneToOne kann eine eins zu eins Beziehung zwischen zwei Entitäten
definiert werden. Eine Seite wird immer als Eigentümer der Relation ausgezeichnet mit Hilfe
des Annotationsattribut mappedBy, das angibt, welches Attribut auf der anderen Seite der
Beziehung der “Fremdschlüssel” ist. In unserem Beispiel ist die Beziehung “betrifft” eine 1:C
Beziehung zwischen “Ausleihe” und “Publikation”.
Beispiel 11.7 [1:C Beziehung] Dieses Beispiel benutzt jpa Annotationen um eine 1:C
Beziehung zwischen Ausleihe und Publikation zu realisieren. Man beachte das Annotationsattribut optional=true, das angibt, dass das Feld auch leer sein darf. In diesem Fall ist die
Klasse Publikation die besitzerin der Beziehung. Der cascadetype ist so gewählt, dass wenn
eine Publikation gelöscht wird, die Ausleihe auch gelöscht wird.
11-10
@Entity
@Table(name="ausleihe")
public class Ausleihe {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int aId;
@Version
private long version;
.
.
@OneToOne(targetEntity=Publikation.class, mappedBy="ausleihe",
cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
private Publikation Publikation;
@PreRemove
public void nullifyReferences() {
this.Publikation.setAusleihe(null);
}
}
@Entity
@Table(name="publikation")
public class Publikation {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int bNr;
@Version
private long version;
.
.
@OneToOne(optional=true, cascade=CascadeType.ALL)
@JoinColumn(name="aid")
private Ausleihe ausleihe;
}
1:M oder M:1 Beziehung
Um eine 1:M Beziehung zu realisieren, definieren wir auf der 1 Seite in der Klasse eine Collection, die alle zu dieser Entität assozierten Entitäten der M Seite enthält. Dieses Attribut
wird mit der Annotation @OneToMany markiert. Auf der M Seite wird ein einfaches Attribut
des Typs der entsprechenden Entität mit der Annotation @ManyToOne markiert. In unserem
Beispiel ist die Beziehung “hat” eine 1:MC Beziehung zwischen “Person” und “Ausleihe”.
Die mit @PreRemove annotierte Methode (siehe Abschnitt 11.2.4) “nullifyReferences” dient
dazu, dass beim Löschen der Ausleihe in der Publikation und in der Person die entsprechende
Referenz auch gelöscht wird.
Beispiel 11.8 [1:MC oder MC:1 Beziehung]
@Entity
@Table(name="personentabelle")
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int pNr;
@Version
private long version;
.
.
// 1:MC Beziehung zur Ausleihe. persist, merge, refresh und remove werden cascadiert
@OneToMany(targetEntity=Ausleihe.class, mappedBy="ausleiher", cascade=CascadeType.ALL)
private Collection<Ausleihe> buecher = new HashSet<Ausleihe>();
}
@Entity
11-11
@Table(name="ausleihe")
public class Ausleihe {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int aId;
@Version
private long version;
.
.
// MC:1 Beziehung zur Person
@ManyToOne( cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
@JoinColumn(name="pNr")
private Person ausleiher;
@PreRemove
public void nullifyReferences() {
this.publikation.setAusleihe(null);
}
}
M:M Beziehung Eine M:M Beziehung wird mit zwei Collections realisiert, die mit der
@ManyToMany Annotation markiert sind. In unserem Beispiel ist die Beziehung “referenziert” eine MC:MC Beziehung zwischen “Publikation” und “Publikation”. Dabei wird mit
dem “mappedBy” Attribut auf der einen Seite die andere Seite als besitzerin der Beziehung
angegeben.
Beispiel 11.9 [M:M Beziehung]
Im folgenden Beispiel wird eine MC:MC Beziehung zwischen Publikationen dargestellt. Dabei
ist die referenzierte Seite die Besitzerin der Beziehung. Mit Hilfe der Annotation @JoinTable
kann der Name und die Atributnamen der Hilfstabelle definiert werden.
@Entity
@Table(name="publikation")
public class Publikation {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int puNr;
@Version
private long version
.
.
// M:M Beziehung zwischen Publikationen.
@ManyToMany(targetEntity=Publikation.class, mappedBy="referenced",
cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
private Collection<Publikation> reference = new HashSet<Publikation>();
// M:M Beziehung zwischen Publikationen. Dies ist die besitzende Seite der Beziehung
@ManyToMany(targetEntity=Publikation.class,
cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
@JoinTable(name="publikation_ref",
joinColumns=@JoinColumn(name="puNr", referencedColumnName="puNr"),
inverseJoinColumns=@JoinColumn(name="refNr", referencedColumnName="puNr"))
private Collection<Buch> referenced = new HashSet<Publikation>();
}
11.2.2.9
Eager oder Lazy Fetching
Wenn ein Objekt aus der Datenbank ins Programm transferiert wird, so können die einzelnen
Attribute sofort eingelesen werden (Eager fetching) oder erst später, wenn sie im Programm
11-12
tatsächlich verwendet werden. Garantiert ist, dass die Werte eingelesen sind wenn das Programm darauf zugreift. Der FetchType kann beim annotieren der Attribute mitgegeben werden. Für die Attribut-Annotationen @Basic, @OneToOne und @ManyToOne ist der Default
FetchType Eager. Für @OneToMany, @ManyToMany und @ElementCollection ist der default
Fetchtype Lazy.
Wenn der Fetchtype Lazy angegeben wird, ist das nur ein Tip für das System. Es ist möglich,
dass die Daten in diesem Fall trotzdem sofort (also Eager) eingelesen werden. Der Fetchtype
Eager hingegen ist für das System verbindlich.
Beispiel 11.10 [Fetchtype]
Im folgenden Beispiel wird bei einigen Attributen der defaut FetchType überschrieben.
@Entity
@Table(name="personentabelle")
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int pNr;
@Version
private long version;
// Fetchtype auf LAZY stellen
@Basic(fetch=FetchType.LAZY)
@Lob
@Column(name="freiertext")
private String text;
// Fetchtype auf EAGER stellen
@ElementCollection(fetch=FetchType.EAGER)
@CollectionTable(name="hobbytabelle",
joinColumns=@JoinColumn(name="pNr"))
private Collection<String> hobbies = new HashSet<String>();
public Person() {}
}
11.2.2.10
Das vollständige Beispiel
In diesem Abschnitt ist das vollständige Beispiel mit den entsprechenden Annotationen angegeben.
Im nächsten Abschnitt sind dieselben Definitionen mit Hilfe eines xml-Files angegeben. In
diesem beispiel werden die Methoden (meistens getter oder setter) nicht angegeben.
Die Klasse Person Speziell zu beachten ist die Definition von Attributen mit Kardinalität
M (hobbies) sowie die Verwendung von CLOBs (text). Weiter hat es in dieser Klasse ein
transienntes Attribut (alter). Dieses Attribut kann z.B. mit der postload callback Routine
berechnet und gesetzt werden.
@Entity
@Table(name="personentabelle")
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int pNr;
@Version
private long version;
@Column(name="familienname", length=30, nullable=false)
private String name;
@Column(length=30, nullable=false)
11-13
private String vorname;
@Temporal(TemporalType.DATE)
@Column(name="geburtsdatum", nullable=false)
private Calendar geburtsDatum;
@Lob
@Column(name="freiertext")
private String text;
@ElementCollection(fetch=FetchType.LAZY)
@CollectionTable(name="hobbytabelle",
joinColumns=@JoinColumn(name="pNr"))
@Column(name = "hobby")
private Collection<String> hobbies = new HashSet<String>();
@OneToMany(targetEntity=Ausleihe.class, mappedBy="ausleiher",
cascade=CascadeType.ALL)
private Collection<Ausleihe> ausleihen = new HashSet<Ausleihe>();
@Transient
private int alter;
public Person() {}
}
Die Klasse Publikation mit den Subklassen Buch und Zeitschrift In diesen Klassen
ist die Vererbung speziell zu beachten. Ferner ist auch die Darstellung von M:M Beziehungen
illustriert.
@Entity
@Table(name="publikation")
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Publikation {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int puNr;
@Version
private long version;
@Basic
@Column(length=30, nullable=false)
private String titel;
@OneToOne(optional=true, cascade=CascadeType.ALL)
@JoinColumn(name="aid")
private Ausleihe ausleihe;
@ManyToMany(targetEntity=Publikation.class, mappedBy="referenced",
cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
private Collection<Publikation> reference = new HashSet<Publikation>();
@ManyToMany(targetEntity=Publikation.class,
cascade={CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
@JoinTable(name="buch_ref",
joinColumns=
@JoinColumn(name="bNr", referencedColumnName="bNr"),
inverseJoinColumns=
@JoinColumn(name="refNr", referencedColumnName="bNr"))
private Collection<Publikation> referenced = new HashSet<Publikation>();
protected Publikation() {}
}
@Entity
@Table(name="buch")
public class Buch extends Publikation {
private String autor;
}
@Entity
11-14
@Table(name="zeitschrift")
public class Zeitschrift extends Publikation {
private int jahr;
private int ausgabe;
@ElementCollection(fetch=FetchType.LAZY)
@CollectionTable(name="artikelliste",
joinColumns=@JoinColumn(name="puNr"))
@Column(name = "title")
private Collection<String> artikel = new HashSet<String>();
}
Die Klasse Ausleihe In dieser Klasse ist speziell die 1-1 Beziehung zwischen ausleihe und
Publikation zu betrachten.
@Entity
@Table(name="ausleihe")
public class Ausleihe {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int aId;
@Version
private long version;
@Temporal(TemporalType.DATE)
@Column(name="ausleihdatum", nullable=false)
private Calendar ausleihDatum;
@OneToOne(mappedBy="ausleihe", cascade=CascadeType.PERSIST)
private Buch buch;
@ManyToOne(cascade=CascadeType.PERSIST)
@JoinColumn(name="pNr")
private Person ausleiher;
protected Ausleihe() {}
@PreRemove
public void nullifyReferences() {
this.buch.setAusleihe(null);
}
}
11.2.2.11
O/R-Mapping mit xml
Mit jpa is es auch möglich, das O/R-Mapping mit Hilfe von xml vorzunehmen. Auch Mischformen sind erlaubt. Wenn sowohl Annotationen wie auch ein xml Definitionsfile vorhanden
sind, so gelten die Annotationen im Sourcecode nur, falls sie im xml File nicht überschrieben
werden. Wir wollen an dieser Stelle nicht die ganze xml Definitionssprache besprechen sondern nur das xml File für die vorige Applikation angeben. Die Definitionen in diesem Beispiel
entsprechen 1 zu 1 den Annotationen in den Klassen.
Beispiel 11.11 [XML] Das folgende Beispiel ist das O/R-Mapping für die Klassen Person,
Publikation, Buch, Zeitschrift und Ausleihe.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd" version="2.0">
<package/>
<access> FIELD </access>
<entity class="latexExamples.Person">
<table name="personentabelle"/>
<attributes>
11-15
<id name="pNr">
<column name="pNr" column-definition="INT"/>
<generated-value strategy="IDENTITY"/>
</id>
<basic name="name">
<column name="familienname" nullable="false" length="30"/>
</basic>
<basic name="vorname">
<column nullable="false" length="30"/>
</basic>
<basic name="geburtsDatum">
<column name="geburtsdatum" nullable="false"/>
<temporal> DATE </temporal>
</basic>
<basic name="text" fetch="LAZY">
<column name="freiertext"/>
<lob/>
</basic>
<version name="version"/>
<one-to-many name="buecher" target-entity="latexExamples.Ausleihe"
mapped-by="ausleiher" fetch="EAGER">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
<element-collection name="hobbies" fetch="LAZY">
<column name="hobby"/>
<collection-table name="hobbytabelle">
<join-column name="pNr"/>
</collection-table>
</element-collection>
<transient name="alter"/>
</attributes>
</entity>
<entity class="latexExamples.Ausleihe">
<table name="ausleihe"/>
<pre-remove method-name="nullifyReferences"/>
<attributes>
<id name="aId">
<column name="aid" column-definition="INT"/>
<generated-value strategy="IDENTITY"/>
</id>
<basic name="ausleihDatum">
<column name="ausleihdatum" nullable="false"/>
<temporal> DATE </temporal>
</basic>
<version name="version"/>
<one-to-one name="buch" mapped-by="ausleihe">
<cascade>
<cascade-persist/>
</cascade>
</one-to-one>
</attributes>
</entity>
<entity class="latexExamples.Buch">
<table name="buch"/>
<attributes>
<id name="bNr">
<column name="bNr" column-definition="INT"/>
<generated-value strategy="IDENTITY"/>
</id>
<basic name="titel">
<column name="titel" nullable="false" length="30"/>
</basic>
<version name="version"/>
<one-to-one name="ausleihe" optional="true">
<join-column name="aid"/>
<cascade>
<cascade-all/>
</cascade>
11-16
</one-to-one>
<many-to-many name="reference" mapped-by="referenced">
<cascade>
<cascade-persist/>
<cascade-merge/>
<cascade-refresh/>
</cascade>
</many-to-many>
<many-to-many name="referenced">
<join-table name="buch_ref">
<join-column name="bNr" referenced-column-name="bNr"/>
<inverse-join-column name="refNr" referenced-column-name="bNr"/>
</join-table>
<cascade>
<cascade-persist/>
<cascade-merge/>
<cascade-refresh/>
</cascade>
</many-to-many>
</attributes>
</entity>
</entity-mappings>
11.2.3
Programmieren mit JPA
Um mit JPA zu arbeiten, braucht es im Programm einen EntityManager. Ein EntityManager
ist mit einem persistence context assoziert. Dies ist eine Menge von persistenten Objekten in
der pro Entitätsidentität höchstens ein Objekt existiert. Das EntityManagerinterface definiert
alle Methoden die nötig sind, um die Objekte im persistence Context zu manipulieren. Zum
Beispiel kreieren von persistenten Objekten, löschen von Objekten, suchen von Objekten über
deren Identität oder absetzen von Queries über persistente Objekte. Die Menge der Entitäten,
die vom Entitymanager manipuliert werden können ist durch eine persistence unit definiert.
In der persistence unit werden alle Klassen definiert, die durch dieselbe Applikation in einer
Datenbank gespeichert werden.
In der nächsten Tabelle sind die wichtigsten Methoden des Entitymanager aufgelistet:
Methode
persist
remove
detach
merge
refresh
find
createQuery
contains
flush
clear
close
getTransaction
Aktion
macht eine neues Objekt persistent
löscht ein persistentes Objekt
löst ein einzelnes Objekt aus dem persistence context heraus
bindet ein Objekt (detached) wieder in den persistence context
ein.
synchronisiert den Inhalt eines Objektes mit den Daten in der
Datenbank
findet ein persistentes Objekt über die Identität
kreiert einen Query um persistente Objekte zu finden
entscheidet, ob ein gegebenes Objket zum gegebenen persistence
context gehört.
schreibt die Daten aller Objekte in die Datenbank zurück (aber
kein commit)
alle Objekte des persistence context werden detached
schliessen des persistence contexts (alle Objekte detached).
gibt ein Transaktionsobjekt zurück. Damit können Transaktionen
gestartet und abgeschlossen werden (begin, commit und rollback).
11-17
11.2.3.1
Das File persistence.xml
Dieses File muss im Subdirectory META_INF des root Directory der applikation stehen. Es
definiert alle Klassen, die zu dieser Unit gehören und falls vorhanden die xml OR-Mapping files
für die entsprechenden Klassen. Ferner können dort auch die Informationen zum öffnen der
richtigen Datenbank gespeichert werden. Nachstehend ist das persistence.xml File für unser
Beispiel angegeben.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="1.0">
<!-A persistence unit is a set of listed persistent entities as well
the configuration of an EntityManagerFactory. We configure each
example in a separate persistence-unit.
-<!-- persistence unit for the "latexExamples" example -->
<persistence-unit name="latexExamples" transaction-type="RESOURCE_LOCAL">
<!-- The file orm.xml contains the OR-Mapping for this example -->
<mapping-file>latexExamples/orm.xml</mapping-file>
<!-- All persistent classes must be listed here -->
<class>latexExamples.Person</class>
<class>latexExamples.Ausleihe</class>
<class>latexExamples.Publikation</class>
<class>latexExamples.Buch</class>
<class>latexExamples.Zeitschrift</class>
<class>latexExamples.Magazine</class>
<properties>
<!-We can configure the default OpenJPA properties here. They
-->
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
<property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost/jpa"/>
<property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/>
<property name="openjpa.ConnectionUserName" value="fep1"/>
<property name="openjpa.ConnectionPassword" value="dozent"/>
<property name="openjpa.Log"
value="File=./log/org.apache.lieferant.log, DefaultLevel=WARN, Runtime=INFO, Tool=INFO"/>
</properties>
</persistence-unit>
</persistence>
11.2.3.2
Beispielprogramm
Das nächste Beispiel zeigt ein vollständiges kleines Programm, dass neue Objekte kreiert
und anschliessend wieder aus der Datenbank hohlt. Der EntityManager erhält man über eine
Factory, welche die Informationen aus dem File persistence.xml liest. Die Factory selbst wird
mit Hilfe der Klasse Persistence kreiert. Dabei muss der Name der persistence unit übergeben
werden.
public class Programmexample {
public static void main(String[] args) {
// Als erstes brauchen wir eine EntityManagerFactory um einen Manager
// zu kreieren. Beim Kreieren uebergeben wir den Namen der
// "persistence unit".
EntityManagerFactory factory = Persistence.
createEntityManagerFactory("latexExamples", System.getProperties());
11-18
// Kreieren des Managers
EntityManager em = factory.createEntityManager();
// Kreieren einer Person
Person p = new Person("Fierz", "Pierre", 1951, 11, 24);
p.setText("Hallo dies ist ein langer Test");
p.addHobby("Golf");
p.addHobby("Fussball");
p.addHobby("Kochen");
// Kreieren zweier Bücher
Buch b1 = new Buch("Die Räuber", "Schiller");
Buch b2 = new Buch("Der Prozess", "Kafka");
// Kreieren einer Zeitschrift
Zeitschrift b3 = new Zeitschrift("Ein Zeitschrift", 2009, 5);
b3.addArtikel("Artikel 1");
b3.addArtikel("Artikel 2");
b3.addArtikel("Artikel 3");
// einige Referenzen
b1.addReference(b2);
b2.addReference(b3);
// Nun noch eine Ausleihe
Ausleihe ba = new Ausleihe(p, b1, Calendar.getInstance());
// Begin der Transaktion
em.getTransaction().begin();
// Persistieren der Ausleihe. Dies wird mit Hilfe von Persistenz durch
// Erreichbarkeit alle kreierten Objekte persistent machen
em.persist(ba);
// Ende der Transaktion
em.getTransaction().commit();
}
}
11.2.4
Lifecycle eines Objektes
In der Abbildung 11-2 ist der Lebenszyklus eines Objektes im Zusammenhang mit JPA
dargestellt.
does not exist
Object ob = new Object()
1.Database remove
2. @PostRemove
new
1. EntityManager.persist(ob)
2. @PrePersist
3. Database insert
4. @PostPersist
serialized or
EntityManager.clear()
detached
1. EntityManager.remove(ob)
2. @PreRemove
3. database remove pending
managed
removed
EntityManager.merge(ob)
1. EntityManager.refresh(ob)
2. @Postload
1. query
2. @Postload
1. change Entity
2. @PreUpdate
3. Database update
4.@PostUpdate
Abbildung 11-2: Entity Lifecycle
11-19
11.2.4.1
Neues Objekt
Neue Objekte werden ganz normal mit new kreiert. Nachdem ein Objekt kreiert wurde ist es
nicht persistent. Es gehört auch nicht zum persistence context eines EntityManagers.
11.2.4.2
Objekt wird persistent
Ein neues Objekt wird durch den Aufruf der persist Methode persistent. Dies kann auch durch
das Kaskadieren der Persistenz geschehen.
Die Methode persist hat die folgende Semantik. Dabei ist X irgend ein Objekt.
• Falls X ein neues Objekt ist, so wird es in den persistence context aufgenommen. Bei
der nächsten commit Operation wird das Objekt in der Datenbank gespeichert.
• Falls X schon im persistence context vorhanden ist, so wird die Operation für dieses
Objekt ignoriert. Jedoch wird die Operation auf neue von X referenzierte Objekte
kaskadiert.
• Falls X ein gelöschtes Objekt ist, so wird es wieder in den persistence context aufgenommen.
• Falls X detached ist, so wird eine EntityExistsException geworfen.
11.2.4.3
Objekt wird verändert
Ein Objekt im persistence context wird verändert indem die Daten in Java wie bei einem
gewöhnlichen Objekt gesetzt werden. Beim nächsten commit werden dann die Veränderungen
in die Datenbank geschrieben.
Achtung bei Assoziationen müssen neue Einträge auf der besitzenden Seite eingetragen sein
damit die neuen Assoziationen auch in die Datenbank gespeichert werden. Dass die Einträge
richtig sind liegt in der Verantwortung der Applikation.
11.2.4.4
Objekt wird gelöscht
Ein Objekt wird durch den Aufruf der Methode remove aus dem persistence context und
auch aus der Datenbank gelöscht. Dies kann auch durch das Kaskadieren der Löschoperation
geschehen.
Die Methode remove hat die folgende Semantik. Dabei ist X irgend ein Objekt.
• Falls X ein neues Objekt ist, so wird die operation ignoriert jedoch wird die Operaqtion
auf von X referenzierte Objekte kaskadiert.
• Falls X im persistence context ist, so wird es gelöscht und die Operation kaskadiert.
• Falls X gelöschst ist, so wird die Operation ignoriert.
• Falls X detached ist, so wird eine IllegalArgumentException geworfen.
• Das Objekt wird bei der nächsten commit Operation aus der Datenbank gelöscht.
11-20
11.2.4.5
Objekt wird detached
Ein Objekt wird durch den Aufruf der Methode detach aus dem persistence context entfernt
(detached). Im Gegensatz zu remove wird aber das Objekt nicht aus der Datenbank entfernt.
Ein solches Objekt existiert weiter im Programm ist aber nicht mehr mit den Daten in der
Datenbank synchroniziert. Die Operationen clear und close entfernen alle Objekte aus dem
persistence context.
Achtung: Falls ein Objekt detached wird, so sind nur die Attribute, die mit fetch=EAGER
und die Attribute auf die explizit zugegriffen wurde sicher im Zustand des Objektes vorhanden.
Für alle anderen Attribute ist dies nicht garantiert.
11.2.4.6
Detached Objekt wird gemerged
Die Operation merge kopiert den Zustand eines (detached) Objekt auf ein Objekt mit derselben Identität im persistence context.
Die Methode merge hat die folgende Semantik. Dabei ist X irgend ein Objekt.
• Falls X detached ist, so wird der Zustand auf ein Objekt mit gleicher Identität im
persistence context kopiert oder es wird eine Kopie des Objekts im persistence context
kreiert.
• Falls X gelöscht ist, so wird eine IllegalArgumentException geworfen.
• Falls X schon im persistence context vorhanden ist, so wird die Operation ignoriert
jedoch wird die Operaqtion auf von X referenzierte Objekte kaskadiert.
11.2.4.7
Queries
Objekte, die in der Datenbank gespeichert sind können mit den Methoden find oder mit einem
Query in den persistence context geladen werden. Nach dem Laden ist nur garantiert, dass
die Attributte die mit fetch=EAGER markiert sind auchwirklich geladen sind.
11.2.5
Callback Methoden
Für persistente Objekte können Callback routinen definiert werden, die an gewissen Punkten
des Lebenszykluses eines Objektes aufgerufen werden. Diese Methoden können entweder in
der Klasse des Objektes definiert werden oder in einer Listenerklasse. Pro Klasse und Ereignis
im Lebenszyklus kann nur eine Callback Methode definiert werden. Dieselbe Methode kann
aber für mehrere Ereignisse verwendet werden. Die Callbackmethoden haben die folgende
Signatur:
• Falls eine Listenerklasse verwendet wird:
void <METHOD>(Object obj)
• Falls die Methode direkt in der Klasse des Objektes geschieben wird:
void <METHOD>(void)
Im folgenden werden die verschiedenen Methoden und der Zeitpunkt ihres Aufrufs beschrieben:
11-21
• Prepersist
Wird aufgerufen gerade bevor das Objekt in den persistence context aufgenommen wird.
Die Methode wird auch für alle Objekte aufgerufen, die durch Kaskadierung persistent
werden.
• PostPersist
Wird aufgerufen nachdem das Objekt in den persistence context aufgenommen wurde
und wird auch für alle Objekte aufgerufen, die durch Kaskadierung persistent werden.
Die Methode wird erst nach dem Insert in der Datenbank aufgerufen. Die Identität des
Objektes steht in der PostPersist Methode in jedem Fall zur Verfügung.
• PreUpdate
Wird aufgerufen, bevor ein verändertes Objekt in die Datenbank zurückgeschrieben
wird.
• PostUpdate
Wird aufgerufen nachdem ein verändertes Objekt in die Datenbank zurückgeschrieben
wurde.
• PreRemove
Wird aufgerufen, bevor ein Objekt aus dem persistence context entfernt und als gelöscht
markiert wird. Die Methode wird auch für alle Objekte aufgerufen, die durch Kaskadierung
gelöscht werden.
• PostRemove
Wird aufgerufen nachdem das Objekt aus der Datenbank gelöscht wurde. Die Methode
wird auch für alle Objekte aufgerufen, die durch Kaskadierung gelöscht werden.
• PostLoad
Diese Methode wird Aufgerufen, wenn ein Objekt aus der Datenbank in den persistence
context geladen wurde oder nach einer refresh Operation.
Beispiel 11.12 [Callback Routine] In der Klasse Person gibt es ein transientes Attribut
alter. Dieses Attribut wird nun mit Hilfe einer PostLoad Callbackmethode abgefüllt.
@Entity
@Table(name="personentabelle")
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int pNr;
@Version
private long version;
// Transientes Attribut wird nicht in der Datenbank gespeichert.
@Transient
private int alter;
@PostLoad
public void computeAlter() {
Calendar y = Calendar.getInstance();
alter = y.get(Calendar.YEAR) - geburtsDatum.get(Calendar.YEAR);
y.add(Calendar.YEAR, -alter);
if (y.before(geburtsDatum)) {
--alter;
}
}
}
11-22
11.2.6
Queries
Als letztes wollen wir sehen, wie persistente Objekte aus der Datenbank ins Programm geladen
werden können.
11.2.6.1
Die find Methode
Find ist eine Methode des EntityManagers und kann benutzt werden um Objekte mit Hilfe
des Schlüssels zu finden. Falls das Objekt schon im persistence context vorhanden ist, so wird
es von dort gehohlt.
Beispiel 11.13 [Find Beispiele] Im folgenden Beispiel werden verschiedene Objekte mit
Hilfe der find Methode ins Programm geladen. Man beachte, dass für Objekte mit zusammengestztem Schlüssel (Magazine) ein Objekt der entsprechenden Id Klasse (MagazineId)
verwendet wird.
{
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
// Die Person mit Schlüssel 1 aus der Datenbank hohlen.
Person p = em.find(Person.class, 1);
// Suchen eines Magazines. Der Schlüssel ist hier zusammengesetzt.
Magazine m = em.find(Magazine.class, new MagazineId("ISBN-3-398373-2",
"Schönes Magazine"));
// Suchen in der Oberklasse
Publikation pu = em.find(Publikation.class, 1);
// Suchen in der Subklasse
Zeitschrift z = em.find(Zeitschrift.class, 1);
em.getTransaction().commit();
}
11.2.6.2
Java persistence query language
Java persistence query Language (JPQL) ist sehr ähnlich wie die Sprache SQL. Der Unterschied ist, dass JPQL auch objektorientierte Elemente hat. So ist es möglich, nicht nur einzelne
Felder zu selektieren sondern ganze Objekte. Ferner kann auch mit dem . Operator durch die
Objekte navigiert werden.
Um ein Query abzusetzen muss zuerst ein Queryobjekt mit der Methode createQuery kreiert
werden. Anschliessend kann mit der Methode getResultset das Resultat erzeugt werden. Diese
Methode liefert eine Liste von Objekten (oder eine Liste von Arrays von Objekten) zurück.
Einfache Queries In den nächsten Beispielen werden einfache Queries vorgestellt bei denen
nur eine Klasse in der Fromklausel vorkommt.
Beispiel 11.14 [Personenobjekte] Selektieren von Nummer, Name und Vorname aller
Personen, die nach dem 1.1.1950 geboren sind und die mindestens eine Publikation ausgeliehen
haben.
{
Query qu = em.createQuery("SELECT p " +
"FROM Person p " +
"WHERE p.geburtsDatum > ’1950.1.1’ AND " +
"p.ausleihen IS NOT EMPTY";
List<Person> lp = qu.getResultList();
for (Person p1 : lp) {
11-23
System.out.println(p1.getPNr() + ", " + p1.getName()+ ", " +
p1.getVorname());
}
}
Das folgende Beispiel liefert das gleiche Resultat. Nur werden jetzt die Attribute pNr, name
und vorname in der SELECT Klausel angegeben. Das Resultat ist dann eine Liste von Arrays
von Objekten.
{
Query qu = em.createQuery("SELECT p.pNr, p.name, p.vorname " +
"FROM Person p " +
"WHERE p.geburtsDatum > ’1950.1.1’ AND " +
"p.ausleihen IS NOT EMPTY\n";);
List<Object[]> op = qu.getResultList();
System.out.println(query);
for (Object[] o : op) {
System.out.println(o[0] + ", " + o[1] + ", " + o[2]);
}
}
Der JPQL Befehl kann auch Platzhalter für Variablen enthalten. Diese können vor dem Aufruf
des Queries mit der Methode setParameter gesetzt werden. Der Platzhalter hat die folgende
Form: ?<nr>
Beispiel 11.15 [Parameter in Queries] Im folgenden Beispiel wird das Datum als Parameter 1 (?1) übergeben.
{
Query qu =em.createQuery("SELECT p " +
"FROM Person p " +
"WHERE p.geburtsDatum > ?1 AND " +
"p.ausleihen IS NOT EMPTY";
Calendar ca = Calendar.getInstance();
ca.set(1950, 1, 1);
qu.setParameter(1, ca);
List<Person> lp = qu.getResultList();
for (Person p1 : lp) {
System.out.println(p1.getPNr() + ", " + p1.getName()+ ", " +
p1.getVorname());
}
}
Kartesisches Produkt In JPQL wird das kartesische Produkt zweier Klassen wie in SQL
gebildet. Dies wird gebraucht, wenn zwei Klassen nicht durch explizite Beziehungen (1:M,
M:M usw.) verbunden sind. Sonst verwendet man eine Join oder IN Operation.
Beispiel 11.16 [Kartesisches Produkt] In diesem Beispiel werden alle Paare von Personen
selektiert, die gleich heissen. Das Resultat ist nach den Personennummern geordnet.
{
Query qu = em.createQuery("SELECT p, p1 " +
"FROM Person p, Person p1 " +
"WHERE p.name = p1.name AND " +
"
p.pNr < p1.pNr " +
"ORDER BY p.pNr";
List<Object[]> op = qu.getResultList();
for (Object[] o : op) {
System.out.println(((Person)o[0]).getPNr() + ", " +
((Person)o[1]).getPNr());
}
}
11-24
Join
sind.
Der Join wird verwendet, wenn zwei Klassen durch eine explizite Beziehung verbunden
Beispiel 11.17 [Join] In nächsten Beispiel werden Personen mit den entsprechenden Ausleihen selektiert, die das Buch “die Räuber” von Schiller ausgeliehen haben.
{
qu =em.createQuery("SELECT p, a " +
"FROM Person p JOIN p.ausleihen a " +
"WHERE trim(a.publikation.titel) = ’Die Räuber’ ";
List<Object[]> op = qu.getResultList();
for (Object[] o : op) {
System.out.println(((Person)o[0]).getPNr() + ", " +
((Ausleihe)o[1]).getPublikation().getTitle());
}
}
Wie in SQL ist Left Join möglich. Im nächsten Beispiel werden auch Personen ausgewählt,
für die keine Ausleihen existieren.
{
qu =em.createQuery("SELECT p, a " +
"FROM Person p LEFT JOIN p.ausleihen a ";
List<Object[]> op = qu.getResultList();
for (Object[] o : op) {
System.out.println(((Person)o[0]).getPNr() + ", " +
(o[1] == null ? "keine Ausleihe"
: ((Ausleihe)o[1]).getPublikation().getTitle()));
}
}
Subqueries
Wie in SQL können in der WHERE Klausel subqueries verwendet werden.
Beispiel 11.18 [Ein Subquery] In diesem Beispiel werden alle Personen die Kochen und
Schreiben als Hobby haben aber sonst kein weiteres Hobby selektiert..
{
Query qu = em.createQuery("SELECT p " +
"FROM Person p, IN(p.hobbies) h1, IN(p.hobbies) h2 " +
"WHERE trim(h1) = ’Kochen’ AND trim(h2) = ’Schreiben’ AND " +
"NOT EXISTS (SELECT h2 FROM IN(p.hobbies) h2 " +
"WHERE trim(h2) <> ’Kochen’ AND trim(h2) <> ’Schreiben’)";
lp = qu.getResultList();
for (Person p1 : lp) {
System.out.println(p1.getPNr() + ", " + p1.getName()+ ", " +
p1.getVorname());
}
}
Group by Auch die Group by und die Having Klausel sind in JPQL vorhanden und haben
dieselbe Bedeutung wie in SQL.
Beispiel 11.19 [Group by Klausel] Im nächsten Beispiel werden alle Personen mit der Anzahl Ausleihen ausgegeben. Auch Personen ohne Ausleihen werden in der Liste aufgenommen.
Die Liste ist nach Anzahl Ausleihen geordnet
{
Query qu =em.createQuery("SELECT p, count(a) AS Anzahl " +
"FROM Person p LEFT JOIN p.ausleihen a " +
11-25
"GROUP BY p " +
"ORDER BY Anzahl";
op = qu.getResultList();
for (Object[] o : op) {
System.out.println(((Person)o[0]).getName() + ", " + o[1]);
}
}
Query mit Konstruktor Ein Query kann auch neue Objekte mit Hilfe eines Konstruktors
kreieren. Die Objekte der entsprechenden Klasse brauchen nicht persistenzfähig zu sein.
Beispiel 11.20 [Konstruktor und Query] Im nächsten Beispiel werden alle Personen in
nicht persistente Objekte des Typs “Pers” eingelesen. Einige Daten dieser Objekte werden
anschliessend ausgegeben.
public
public
public
public
public
public
class Pers {
int pNr;
String name;
String vorname;
Calendar geburtsDatum;
String text;
public Pers(int pNr, String name, String vorname,
Calendar geburtsdatum, String text) {
this.pNr = pNr;
this.name = name;
this.vorname = vorname;
this.geburtsDatum = geburtsdatum;
this.text = text;
}
}
{
qu =em.createQuery("SELECT new latexExamples.Pers( \n" +
"
p.pNr, p.name, p.vorname, p.geburtsDatum, p.text) \n" +
"FROM Person p");
List<Pers> per = qu.getResultList();
for (Pers pe : per) {
System.out.println(pe.name + " " + pe.vorname + " " +
pe.geburtsDatum.getTime());
}
}
11-26
11.3
Übungen
Aufgabe 11.1 [Lieferanten Datenbank]
1. Schreiben Sie Java Klassen für das Lieferantenbeispiel (Abschnitt A.1). Dabei sollen alle
Primärschlüssel automatisch generiert werden.
2. Schreiben Sie ein xml File, das das OR-mapping definiert.
3. Realisieren Sie das OR-Mapping auch mit Hilfe von Annotations in den Klassen.
Achtung: Die Zwischentabelle b_p muss folgendermassen angepasst werden:
CREATE TABLE b_p (
po
integer not null autoincrement
b_nr
integer not null,
p_nr
integer not null,
menge
integer not null,
PRIMARY KEY (po),
FOREIGN KEY (b_nr) REFERENCES bestellung (b_nr)
ON DELETE CASCADE,
FOREIGN KEY (p_nr) REFERENCES produkt (p_nr),
UNIQUE (b_nr, p_nr),
CHECK(menge > 0)) ENGINE=InnoDB
11-27
11-28
Kapitel 12
Security (Datenschutz)
Die Begriffe “Datenschutz” und “Datenintegrität” werden im Zusammenhang mit Datenbanken sehr oft gleichzeitig genannt, obschon beide Begriffe sehr verschiedene Dinge bezeichnen.
• Der Datenschutz hat dafür zu sorgen, dass ein Benutzer nur die für ihn auf der Datenbank
erlaubten Operationen ausführen kann.
• Die Datenintegitätskontrolle hat dafür zu sorgen, dass diese Operationen korrekt ausgeführt werden.
Der Datenschutz seinerseits zerfällt in zwei wichtigen Kategorien
1. Schutz der Daten gegenüber unberechtigtem Zugriff.
In einer Datenbank sind oft sehr viele strategische (z.B. Marktstrategien) und operative
(z.B. Fabrikationsgeheimnisse) Informationen über eine Unternehmung gespeichert, die
man natürlich nicht der Öffentlichkeit preisgeben will. Das heisst, das System muss
Instrumente zur Verfügung stellen, um diese Daten vor unbefugtem Zugriff zu schützen.
2. Schutz des Einzelnen gegen die über ihn gespeicherten Daten (Persönlichkeitsschutz).
Dieser Aspekt des Datenschutzes beinhaltet sowohl ethische wie auch gesetzliche Gesichtpunkte und ist genau so wichtig wie der Schutz der Daten vor unberechtigtem Zugriff
(wenn nicht sogar wichtiger).
In den zwei nächsten Abschnitten wollen wir diese zwei Aspekte näher betrachten.
12.1
Schutz vor unberechtigtem Zugriff
Dieser Aspekt des Datenschutzes hat viele Komponenten, die nichts mit dem Datenbanksystem selbst zu tun haben und die meistens vom Betreiber der Datenbank gelöst werden müssen.
Das Datenbanksystem stellt nur Instrumente zur Verfügung um die Lösung dieser Probleme
zu unterstützen. Bevor mit dem Betrieb einer Datenbank begonnen werden kann, müssen die
folgenden Fragen beantwortet sein.
• Welche Daten müssen in welchem Masse geschützt werden?
• Sind gesetzliche Vorlagen einzuhalten?
12-1
• Welche Benutzer dürfen auf welche Daten zugreifen und wie (Schreiben und Lesen oder
nur Lesen usw.)?
• Sind physische Massnahmen wie Terminalschlüssel oder abgeschlossene Räume vorzusehen?
• Wie wird garantiert, dass die Passwörter der Benutzer geheimgehalten werden?
Wir wollen nun die verschiedenen Instrumente kennenlernen, die von Datenbanksystemen zur
Unterstützung des Datenschutzes zur Verfügung gestellt werden.
12.1.1
Benutzer
Datenschutz macht nur Sinn, wenn das DBMS die Benutzer der Datenbank kennt. Bevor
Benutzer definiert sind, können auch keine Rechte verteilt werden. Das heisst, dass sich ein
Benutzer immer bei der Datenbank mit Name und Passwort anmelden muss, bevor er überhaupt auf die Daten zugreifen kann. Es ist für den Datenschutz sehr wichtig, dass das Passwort
eines Benutzers nur diesem Benutzer bekannt ist.
Bemerkung 12.1 [Passwort] Viele DBMS benutzen direkt die Benutzer- und Passwortverwaltung des zugrundeliegenden Betriebssystems.
Das Schutzsystem eines DBMS ist immer hierarchisch aufgebaut. Es braucht immer mindestens einen Benutzer, der selbst alle DBMS Operationen ausführen darf. Insbesondere ist
er in der Lage anderen Benutzern Rechte zuzuweisen. Dieser Benutzer wird meistens als
Datenbankadministrator (DBA) bezeichnet. Ausser dem DBA existieren vorerst keine anderen berechtigten Benutzer des DBMS. Der DBA kann nun weitere Benutzer auf das DBMS
zulassen und ihnen auch Administrationsaufgaben übertragen. Insbesondere können Benutzer
dazu berechtigt werden, eine neue Datenbank zu kreieren.
Ein weiteres wichtiges Konzept für den Datenschutz ist der Besitzer eines Datenbankobjekts.
Hat ein Benutzer vom DBA die Erlaubnis erhalten, Objekte im DBMS zu kreieren, so ist dieser
Benutzer der Besitzer der Objekte, die er im DBMS erzeugt (dies kann eine Datenbank, eine
Tabelle in einer Datenbank usw. sein). Der Besitzer eines Objekts besitzt automatisch alle
sinnvollen Zugriffsrechte auf dieses Objekt und kann diese an weitere Benutzer des DBMS
weitergeben.
12.1.2
Datenschutz und SQL
Im Nachfolgenden wollen wir sehen, wie mit Hilfe von SQL die Zugriffsrechte auf die eigentlichen
Daten in den einzelnen Relationen einer Datenbank verteilt werden können. Normalerweise
werden diese Rechte vom Besitzer der entsprechenden Datenbank erteilt.
In SQL werden Zugriffsrechte mit den Befehlen GRANT und REVOKE zugeteilt, beziehungsweise
wieder zurückgenommen. Ein Zugriffsrecht besteht immer aus drei Komponenten: Einem
Datenbankobjekt (eine Tabelle, ein Attribut einer Tabelle), einer Operation, die darauf erlaubt
ist und dem Benutzer, dem das Recht zugeteilt wird. Die Syntax des Befehls lautet:
<grantstatement> ::=
{ALL PRIVILEGES|<oplist>}
ON TABLE <tablelist>
TO <userlist>| PUBLIC
[WITH GRANT OPTION]
GRANT
12-2
<oplist>
::=
SELECT
|
[(<attributlist>)] |
[(<attributlist>)] |
DELETE |
REFERENCE [<attributelist>]
UPDATE
INSERT
Die Angabe von ALL PRIVILEGES anstelle einer Liste von Operationen bedeutet, dass alle
Operationen auf den angegebenen Tabellen für die gewünschten Benutzer zugelassen sind. Die
Angabe von PUBLIC anstelle einer Liste von Benutzer bedeutet, dass die angegebenen Rechte
für alle Benutzer der Datenbank gelten.
Die Bedeutung der einzelnen Privilegien ist im Folgenden angegeben:
SELECT: Lesezugriff auf alle Tupel einer Relation.
INSERT: Einfügen von neuen Tupeln in der gegebenen Relation. Wird eine Attributliste
angegeben, so dürfen nur Werte für diese Attribute eingefügt werden.
UPDATE: Ändern von Tupeln in der gegebenen Relation. Wird eine Attributliste angegeben,
so dürfen nur Werte für diese Attribute verändert werden.
DELETE: Löschen von Tupeln in der gegebenen Relation.
REFERENCE: Die Attributte der gegebenen Relation dürfen in Integritätsbedingungen
referenziert werden. Wird eine Attributliste angegeben, so dürfen nur diese Attribute
referenziert werden.
Beispiel 12.1 [Grant Beispiele] Nachstehend sind einige Beispiele angegeben.
GRANT ALL PRIVILEGES ON TABLE
GRANT SELECT ON TABLE
lieferant
lieferant
TO
Frey, Meier
TO PUBLIC
Nach diesen Befehlen haben die Benutzer Frey und Meyer alle Zugriffsrechte auf den Lieferanten. Andere Benutzer der Datenbank dürfen diese Relation lesen.
GRANT SELECT, UPDATE
(lagermenge)
ON TABLE
produkt
TO
Frey
Nach diesem Befehl kann der Benutzer Frey Produkte lesen und das Attribut lagermenge
verändern. Die anderen Attributwerte kann er aber nicht verändern.
Eine mit GRANT erteilte Zugriffsberechtigung kann mit dem
gemacht werden. Die allgemeine Form des Befehls lautet:
<revokestatement> ::=
REVOKE
Befehl rückgängig
{ALL | <oplist>} ON TABLE <tablelist>
<userlist> | PUBLIC
{RESTRICT | CASCADE}
REVOKE
FROM
12-3
Bemerkung 12.2 [Grant auf Views] Die Befehle GRANT und REVOKE können sowohl auf
Basistabellen wie auch auf Views angewandt werden.
Wir wollen hier noch die Bedeutung der Klausel WITH GRANT OPTION erläutern. Ist diese
Klausel angegeben, so werden nicht nur die angegebenen Zugriffsrechte erteilt, sondern es
wird auch dem entsprechenden Benutzer das Recht erteilt, die entsprechenden Zugriffsrechte
an weitere Benutzer zu erteilen.
Beispiel 12.2 [Grant Option] Im folgenden Beispiel wird vom Besitzer der Relation lieferant
dem Benutzer Frey alle Rechte auf diese Relation erteilt.
GRANT ALL PRIVILEGES ON TABLE
lieferant
TO
Frey
WITH GRANT OPTION
Der Benutzer Frey hat jetzt nicht nur alle Zugriffsrechte auf den Lieferanten, er kann diese
(oder einen Teil davon) an weitere Benutzer weitergeben. Er kann etwa den folgenden Befehl
ausgeben:
GRANT SELECT ON TABLE
lieferant
TO
Meyer
WITH GRANT OPTION
Nun darf der Benutzer Meyer die Lieferanten lesen und darf dieses Recht an weitere Benutzer
weitergeben. Dieser Mechanismus kann beliebig kaskadiert werden.
Wir stellen nun die Frage was passiert, wenn der Besitzer der Relation lieferant die Zugriffsrechte von Frey zurücknehmen will. Auch beim REVOKE Befehl muss eine der Klauseln
RESTRICT oder CASCADE angegeben werden. In unserem Fall würde der Befehl
REVOKE ALL ON TABLE
lieferant
FROM
Frey
RESTRICT
fehlschlagen, weil Frey einen Teil der Privilegien weitergegeben hat. Um diese Rechte zu
löschen, muss der Besitzer den Befehl
REVOKE ALL ON TABLE
lieferant
FROM
Frey
CASCADE
angeben. Dies bewirkt nun, dass die Zugriffsrechte auf den Lieferanten für den Benutzer Frey
gelöscht werden. Hat Frey diese Rechte an weitere Benutzer weitergegeben, so werden diese
Zugriffsrechte auch gelöscht. Dieser Vorgang wird kaskadiert.
12.1.3
Datenschutz und Views
Mit den Befehlen GRANT und REVOKE kann der Zugriff auf Datenbankobjekte als Ganzes
geregelt werden. Was hingegen nicht möglich ist, ist der Schutz auf Tupelebene. Das heisst,
es ist nicht möglich den Zugriff auf Tupel in Abhängigkeit der im Tupel gespeicherten Werte
zu regeln.
Beispiel 12.3 [Probleme] Wir nehmen an, dass für die Bewirtschaftung der Lieferanten in
unserer Datenbank zwei Sachbearbeiter zuständig sind. Der Sachbearbeiter Frey bearbeitet
alle Lieferanten mit einer Postleitzahl kleiner als 4000 und der Sachbearbeiter Meyer bearbeitet
alle anderen. Wir möchten nun erreichen, dass jeder Sachbearbeiter nur seine Lieferanten lesen
und verändern kann. Dies kann mit dem GRANT Befehl allein nicht erreicht werden.
12-4
Solche Probleme lassen sich nur mit Hilfe von Views lösen.
Beispiel 12.4 [Benutzung von Views] Das Problem im vorigen Beispiel kann mit Hilfe
von Views und dem GRANT Befehl folgendermassen gelöst werden.
1. Zuerst werden beiden Benutzern alle Rechte auf die Relation lieferant weggenommen.
REVOKE ALL ON TABLE
lieferant
FROM
Frey, Meyer
2. Nun werden zwei Views kreiert.
CREATE VIEW
lieferant_1
AS
SELECT
FROM
WHERE
*
lieferant
plz < 4000
WITH CHECK OPTION
CREATE VIEW
lieferant_2
AS
SELECT
FROM
WHERE
*
lieferant
plz >= 4000
WITH CHECK OPTION
3. Nun können die entsprechenden Zugriffsrechte erteilt werden.
GRANT ALL PRIVILEGES ON TABLE
lieferant_1
TO
Frey
GRANT ALL PRIVILEGES ON TABLE
lieferant_2
TO
Meyer
Nun sieht jeder Sachbearbeiter genau seine Lieferanten. Man beachte, dass die Views mit der
WITH CHECK OPTION Klausel definiert sind. Das heisst, die Postleitzahl eines Lieferanten
kann in diesen Views nicht beliebig verändert werden.
Im obigen Beispiel sind beide Views veränderbar (siehe Kapitel ??), so dass jeder Sachbearbeiter wie auf einer normalen Relation arbeiten kann.
12.1.4
Netzwerke
Sehr oft wird beim Aufbau des Datenschutzes vergessen, dass Daten, die über Netzwerke
ausgetauscht werden, schlecht oder sogar nicht geschützt sind. Daher müssen Daten die über
Netzwerke (vor allem öffentliche) ausgetauscht werden, unbedingt chiffriert werden. Heute
existieren dazu relativ sichere Verfahren.
12.2
Persönlichkeitsschutz
Auch dieser Aspekt des Datenschutzes muss der Informatiker berücksichtigen. C.A. Zehnder
meint in [Zeh89] dazu:
12-5
Der Informatiker als der technisch Verantwortliche für Datensysteme darf sich
nicht mit einer sauberen technischen Leistung zufriedengeben, solange er nicht
dem Anwender geholfen hat, sein neues Instrument, das Datenbanksystem, verantwortungsvoll zu benützen.
Weiter gibt Zehnder allgemeine Grundsätze für den Datenschutz an. Die folgenden Betrachtungen betreffen vor allem Daten, die im Zusammenhang mit Personen stehen.
• Für Personaldaten jeder Art müssen Ziel und Zweck der gespeicherten Daten klar bestimmt sein. Allfällige Rechtsgrundlagen, Vertragsbestimmungen oder Zweckartikel sind
dafür massgebend. Zweckänderungen von Daten sind nur zulässig, wenn die betroffenen
Personen informiert werden und damit einverstanden sind.
• Die Speicherung heikler Daten, die eine Person besonders treffen können, ist nur beschränkt
zulässig.
• Sammlungen von Personaldaten sind in einem bestimmten Rahmen zu registrieren. Ein
Auskunftsrecht erlaubt jedermann, die ihn betreffenden Daten in einer Datensammlung
einzusehen.
• Falsche oder unvollständige Daten sind zu berichtigen oder zu ergänzen. Unzulässige
oder nicht mehr benötigte Daten sind zu vernichten.
• Besondere Sorgfaltspflichten bestehen bei der allfälligen Weitergabe von Daten. Weitergabe von Informationen an Dritte dürfen nicht ohne das Wissen der betroffenen Personen
erfolgen.
Diese Grundsätze gelten im Prinzip für jede Datensammlung von Personaldaten (nicht nur
für Datenbanken). Aber einige Aspekte erhalten bei Datenbanken ganz besondere Bedeutung.
Die Brisanz des Computereinsatzes für grosse Bestände von Personendaten besteht in der
Kombination folgender Eigenschaften:
• Die Möglichkeit des schnellen Suchens in grossen Datenbeständen nach verschiedenen
Kriterien.
• Die Möglichkeit, verschiedene Tatsachen nach verschiedenen Kriterien zu verknüpfen
(auch über verschiedene Datenbanken hinweg).
• Gefahr des Einbezugs von falschen Daten, die auch zu falschen Schlüssen über eine
Person führen können. Bei sehr grossen Datenbeständen kann die Korrektheit der Daten
nicht mehr manuell überprüft werden.
• Es besteht auch die Gefahr, dass Daten in unzulässiger Weise verknüpft oder verglichen
werden (nicht kompatible Daten).
Mit Hilfe der in Abschnitt 12.1.2 und 12.1.3 vorgestellten Techniken ist es möglich, solche
Missbräuche zu verhindern. Da aber diese Instrumente vom Besitzer der Datenbank bedient
werden, ist eine effiziente Überwachung des Persönlichkeitsschutzes sehr schwierig.
Eine weitere Möglichkeit den Persönlichkeitsschutz zu verbessern, ist eine Föderalistische Organisation der Daten. Wir können dies nach Zehnder folgendermassen umschreiben:
12-6
Datenföderalismus bezeichnet eine Organisationsform für Daten in grossen Organisationen, bei welcher Teildatenbereiche autonom organisiert werden, aber unter
genau definierten Voraussetzungen zusammenarbeiten können.
Beispiel 12.5 [Verwaltung] Als Beispiel für solche Lösungen eignen sich die Verwaltungen
aller als Bundesstaaten organisierten Länder. In der Schweiz sind autonome Verwaltungssysteme der Normalfall:
Gemeinde: Einwohnerkontrolle, Steueramt, AHV-Stelle, Sektionschef,. . .
Kanton: Polizei, Strassenwesen, Gebäudeversicherung, kantonale Steuer,. . .
Bund: AHV, Fremdenpolizei, Militär, Bundessteuer,. . .
Natürlich haben diese Systeme Kontakt miteinander (in der Schweiz manchmal zu wenig),
etwa beim Militär oder bei den Steuern, aber die Datensysteme sind gegenseitig nicht direkt
zusammengeschlossen. Zum Teil wäre das sogar gesetzlich verboten (etwa Datenweitergabe
von AHV ans Steueramt).
Zu diesem Beispiel noch einige Bemerkungen:
• In allen Datenbanken könnten aber datenschutzmässig unkritische Datenarten (etwa
Identifikation, Name, Geburtsdatum, Adresse) zentral verwaltet werden (etwa in Form
einer zentralen Adressdatei pro staatliche Einheit).
• Datenschutzmässig kritische Daten (etwa Gesundheits- und Polizeidaten) sollten hingegen nicht in die zentrale Verwaltungsdatenbank übernommen werden um einen optimalen Datenschutz zu gewährleisten.
• Werden ämterübergreifende Verwaltungsdatenbanken gebildet, muss die Verantwortung
für den Zugriffsschutz auf dieser Datenbank übernehmen.
Bemerkung 12.3 [Föderative Organisation] Die föderative Organisationsform hilft nicht
nur bei der Lösung von Datenschutzproblemen, sondern ist auch dazu geeignet die Komplexität von sehr grossen Informationssystemen zu reduzieren.
12-7
8
Anhang A
Beispiele
A.1
Lieferantenbeispiel
Fast alle Beispiele in diesem Skript beziehen sich auf die hier beschriebene sehr einfache
Datenbank, welche Lieferanten mit ihren Bestellungen beschreibt.
A.1.1
Lieferanten ERD
Das ERD für die Lieferanten Datenbank ist in der Abbildung A-1 angegeben.
<<entity>>
bestellung
b_nr
bestelldatum
lieferdatum
0..*
<<entity>>
lieferant
gehoert zu
<<entity>>
produkt
p_nr
bezeichnung
jahrgang
liefereinheit
lagermenge
min_lagermenge
l_nr
name
1 plz
ort
0..*
besteht aus
1..*
<<entity>>
b_p
menge
Abbildung A-1: Lieferanten Datenbank
Dazu noch die folgenden Erklärungen:
• Zu einem Lieferanten existieren im allgemeinen mehrere Bestellungen (es ist aber auch
möglich, dass keine existiert).
• Eine Bestellung ist offen, solange kein Lieferdatum eingetragen ist. Ist das Lieferdatum eingetragen heisst das, dass die gesammte Bestellung geliefert worden ist (keine
Teillieferungen möglich).
• Eine Bestellung besitzt mindestens einen Bestellposten. Die Mengenangabe im Bestellposten ist in Liefereinheiten gegeben. Um die effektive Lagermenge zu bekommen muss
also diese Zahl mit der Liefereinheit im Produkt multipliziert werden.
A-1
• Das Feld in min_Lagermenge gibt an, ab welchem Bestand ein Produkt nachbestellt
werden muss.
A.1.2
Darenbankschema Lieferanten
In der folgenden Aufstellung sind alle Relationenschematas für das Beispiel angegeben.
lieferant
produkt
bestellung
b_p
A.1.3
= ({l_nr, name, ort, plz}, {l_nr})
= ({p_nr, bezeichnung, jahrgang, liefereinheit,
lagermenge, min_lagermenge}, {p_nr},
ks({bezeichnung, jahrgang})
= ({b_nr, l_nr, bestelldatum,
lieferdatum nw}, {b_nr},
fs({l_nr}, lieferant, res))
= ({b_nr, p_nr, menge}, {b_nr, p_nr},
fs({b_nr}, bestellung, cas),
fs({p_nr}, produkt, res))
Daten zur Lieferantendatenbank
In diesem Abschnitt sind zur Applikation die Daten in Tabellenform angegeben. Diese Daten
stehen auch in der Übungsdatenbank zur Verfügung (Kapitel 5).
lieferant
l_nr
1
2
3
4
5
6
7
8
9
10
11
12
13
name
Dettwiler
Haller
Walter
Glauser
Favre
Meier
Meier
Meier
Grobet
Desbiole
Arnold
Aeberhard
Morin
ort
Bern
Thun
Thun
Bern
Lausanne
Bern
Winterthur
Biel
Genf
Genf
Sion
Bern
Genf
A-2
plz
3016
3604
3602
3012
1205
3008
8402
3210
1003
1005
1950
3014
1007
produkt
p_nr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bezeichnung
Chardonnay
Chardonnay
Riesling
Riesling
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Pinot Noir
Pinot Blanc
Gamay
Luins
Luins
Dole
Dole
Fechy
Armagnac 45%
Cognac 40%
jahrg
2003
2006
2006
2005
1997
1998
1999
2002
2002
2006
2005
2005
2006
2003
2004
2006
1948
1989
liefereinheit
6
6
12
12
1
1
3
6
12
24
12
6
12
4
6
12
1
1
lagermenge
513
320
217
127
97
167
575
123
339
1244
627
54
221
124
283
597
13
155
min_l
30
60
36
12
75
44
33
60
36
24
60
60
36
20
24
36
20
10
bestellung
b_nr
1
9
13
14
15
2
10
11
12
3
16
17
4
5
6
7
19
8
18
l_nr
1
1
1
1
1
2
2
2
2
3
3
3
4
5
6
7
7
8
9
bestelldatum
2006-06-05
2007-02-11
2007-03-11
2007-04-12
2007-05-10
2006-09-23
2007-04-21
2007-03-24
2007-05-10
2006-09-18
2007-01-31
2007-04-21
2006-07-18
2006-01-02
2006-09-02
2006-12-29
2007-04-23
2006-11-26
2007-05-03
lieferdatum
2006-07-11
2007-03-22
2006-09-26
2006-09-22
2007-03-21
2006-08-22
2006-02-04
2006-09-10
2007-01-11
-
Die folgende Tabelle existiert nicht direkt in der Datenbank, sondern ist mit Hilfe von relationalen Operationen aufgebaut. Sie zeigt die Bestellungen pro Lieferant, sowie die einzelenen
Bestellposten. Die beiden Felder b_nr und p_nr entsprechen der Postentabelle b_p in der
Datenbank.
A-3
Lieferanten und ihre Bestellungen
l_nr
1
2
3
name
Dettwiler
Haller
Walter
b_nr
1
bdatum
2006-06-05
ldatum
2006-07-11
9
2007-02-11
2007-03-22
13
2007-03-11
-
14
15
2007-04-12
2007-05-10
-
2
2006-09-23
2006-09-26
10
2007-04-21
-
11
2007-03-24
-
12
3
2007-05-10
2006-09-18
2006-09-22
16
2007-01-31
2007-03-21
17
2007-04-21
-
4
Glauser
4
2006-07-18
2006-08-22
5
Favre
5
2006-01-02
2006-02-04
6
Meier
6
2006-09-02
2006-09-10
7
Meier
7
19
2006-12-29
2007-04-23
-
A-4
p_nr
1
7
15
1
2
1
9
10
9
2
7
8
10
13
3
9
1
2
3
11
12
13
7
2
4
11
14
8
9
10
1
3
4
6
5
6
7
8
5
8
15
12
12
14
bezeichnung
Chardonnay
Cab. Sauvignon
Dole
Chardonnay
Chardonnay
Chardonnay
Pinot Noir
Pinot Blanc
Pinot Noir
Chardonnay
Cab. Sauvignon
Cab. Sauvignon
Pinot Blanc
Luins
Riesling
Pinot Noir
Chardonnay
Chardonnay
Riesling
Gamay
Luins
Luins
Cab. Sauvignon
Chardonnay
Riesling
Gamay
Dole
Cab. Sauvignon
Pinot Noir
Pinot Blanc
Chardonnay
Riesling
Riesling
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Cab. Sauvignon
Dole
Luins
Luins
Dole
m
17
12
3
22
34
12
99
33
27
4
13
12
3
1
33
7
37
11
12
6
3
22
12
4
12
3
22
2
21
3
25
7
12
2
7
33
101
77
22
13
3
3
23
12
Lieferanten und ihre Bestellungen Fortetzung
l_nr
A.2
name
8
Meier
9
Grobet
b_nr
bdatum
ldatum
8
2006-11-26
2007-01-11
18
2007-05-03
-
p_nr
bezeichnung
1
4
12
7
8
Chardonnay
Riesling
Luins
Cab. Sauvignon
Cab. Sauvignon
m
2
22
21
50
23
Personenbeispiel
Übungen mit rekursiven Daten und die Übungen zur relationalen Algebra in diesem Skript
beziehen sich fast alle auf die hier beschriebene sehr einfache Personendatenbank.
A.2.1
Personen ERD
Das ERD zur Personendatenbank ist in der Abbildung A-2 angegeben.
person
{persistent} 1
0..1
ist Mutter
0..*
p_nr
name
vorname
plz
ort
geburtsdatum
betreibt
0..*
0..1
pers_hobby
{persistent}
p_nr
hobby
ist Vater
0..*
Abbildung A-2: Personen Datenbank
A.2.2
Datenbankschema Personen
In der folgenden Aufstellung sind alle Relationenschematas für das Personenbeispiel angegeben.
person
= ({p_nr, name, vorname, plz, ort,
geburtsdatum, mutter nw, vater nw}, {p_nr},
fs({mutter}, person, nul),
fs({vater}, person, nul))
pers_hobby = ({p_nr, hobby}, {p_nr, hobby},
fs({p_nr}, person, cas))
A.2.3
Daten
Hier sind noch die in der Datenbank gespeicherten Personen und Hobbies.
A-5
person
p_nr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name
Mueller
Meyer
Ernst
Schmid
Meier
Meier
Glauser
Meier
Meier
Glauser
Meier
Glauser
Rohner
Glauser
Rohner
Meier
Meier
Glauser
Glauser
Glauser
Rohner
Rohner
Meier
vorname
Hans
Peter
Ernst
Mario
Hans
Frida
Carmen
Peter
Franz
Max
Paula
Rolf
Emanuela
Martha
Heinz
Daniel
Anna
Thomas
Christian
Stefan
Markus
Pia
Marianne
plz
8012
3006
4007
3006
3006
3006
3601
1210
3012
3601
1210
8403
3210
8403
3210
1012
1012
8403
8403
8403
3210
3210
1012
ort
Zuerich
Bern
Basel
Bern
Bern
Bern
Thun
Lausanne
Bern
Thun
Lausanne
Winterthur
Biel
Winterthur
Biel
Genf
Genf
Winterthur
Winterthur
Winterthur
Biel
Biel
Genf
geburtsdatum
1955-07-11
1971-02-28
1937-08-26
1949-02-24
1920-12-22
1924-03-11
1946-06-05
1947-08-15
1950-04-29
1944-05-11
1948-04-11
1970-02-26
1968-08-08
1973-07-01
1964-05-04
1972-03-07
1976-07-13
1992-06-03
1994-08-17
1997-01-01
1990-09-13
1994-10-21
2000-02-21
mutter
6
6
6
7
7
11
14
14
14
13
13
17
vater
5
5
5
10
10
8
12
12
12
15
15
16
Die folgende Tabelle existiert in der Datenbank nicht direkt. Sie wurde mit Hilfe von Operationen zusammengestellt und zeigt alle Personen, die Hobbies betreiben.
Personen mit Hobbies
p_nr
name
vorname
plz
ort
hobby
1
Mueller
Hans
8012
Zuerich
2
Meyer
Peter
3006
Bern
3
Ernst
Ernst
4007
Basel
4
Schmid
Mario
3006
Bern
Fussball
Theater
Klavierspielen
Fussball
Boxen
Theater
Golf
Theater
Klavierspielen
A-6
Abbildungsverzeichnis
1-1 Zugriff auf gemeinsamen Daten in einem Filesystem . . . . . . . . . . . . . . . 1-1
1-2 Das DBMS steht zwischen Daten und Benutzern . . . . . . . . . . . . . . . . . 1-2
1-3 Die 3-Ebenen-Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-4
2-1 Entsprechung Einzelfall – mehrere Einzelfälle . . . . . . . . . . . . . . . . . . . 2-5
2-2 Darstellung von Entitätsmengen und Attributen . . . . . . . . . . . . . . . . . 2-9
2-3 Darstellung von Beziehungsmengen (M:M und M:1) . . . . . . . . . . . . . . . 2-12
2-4 Beziehungsattribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-13
2-5 Darstellung der verschiedenen Überlagerungen
. . . . . . . . . . . . . . . . . . 2-14
2-6 Darstellung der Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-15
2-7 ERD zum Kursproblem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-21
3-1 Die Relation Lieferant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-4
3-2 Fremdschlüssel X(bestellung) → Y (lief erant)
. . . . . . . . . . . . . . . . . . 3-8
3-3 Die Beziehungsmenge b_p . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-9
4-1 Die Selektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-2
4-2 Resultat einer Selektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-3
4-3 Resultat einer Selektion mit Vergleich zweier Attribute . . . . . . . . . . . . . . 4-3
4-4 Die Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-4
4-5 Resultat der Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-4
4-6 Der Verbund . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-5
4-7 Resultat des natürlichen Verbunds . . . . . . . . . . . . . . . . . . . . . . . . . 4-5
4-8 Die Mengenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-6
4-9 Resultat einer Vereinigung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-6
4-10 Resultat eines Selfjoins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-7
4-11 Relation als Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-9
4-12 Transitive Hülle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-10
4-13 Syntaxbaum vor der Optimierung . . . . . . . . . . . . . . . . . . . . . . . . . . 4-12
4-14 Syntaxbaum mit optimierten Selektionen
. . . . . . . . . . . . . . . . . . . . . 4-12
4-15 Syntaxbaum mit optimierten Projektionen . . . . . . . . . . . . . . . . . . . . . 4-13
A-7
4-16 Optimierter Syntaxbaum
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-13
7-1 Die Entitätsmenge Person . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-11
7-2 Darstellung von Beziehungsmengen . . . . . . . . . . . . . . . . . . . . . . . . . 7-15
7-3 Beziehungsattribut bei einer 1 zu M Beziehung . . . . . . . . . . . . . . . . . . 7-17
7-4 Beziehungsattribut bei einer textbfM zu M Beziehung . . . . . . . . . . . . . . 7-17
7-5 Namenskonflikte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-18
7-6 Das Personen-Produkt-Maschine Beispiel
. . . . . . . . . . . . . . . . . . . . . 7-23
7-7 ERD zum Kurssystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-26
7-8 Kunden-Fakturen-Artikel Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . 7-27
9-1 Package-Struktur von JDBC™ . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-2
9-2 Das Package java.sql . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-3
10-1 Recovery mit Hilfe des Logfiles . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-4
10-2 2 Phasen Commit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-6
10-3 Update von Transaktion A wird zur Zeit t4 überschrieben . . . . . . . . . . . . 10-8
10-4 Probleme mit nicht abgeschlossenen Transaktionen . . . . . . . . . . . . . . . . 10-8
10-5 Verlust des Updates wegen einer nicht abgeschlossenen Transaktion . . . . . . . 10-9
10-6 Transaktion A berechnet einen falschen Wert (inconsistent analysis) . . . . . . 10-9
10-7 Kompatibilität von Locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-10
10-8 Lost Update Problem mit Locking . . . . . . . . . . . . . . . . . . . . . . . . . 10-10
10-9 Transaktion B muss warten, bis Transaktion A beendet ist . . . . . . . . . . . . 10-11
10-10Transaktion B muss mit dem UPDATE warten, bis Transaktion A beendet ist. 10-11
10-11Keine Inkonsistenz mehr aber, es entsteht ein Deadlock . . . . . . . . . . . . . 10-12
11-1 Beispiel Personen und Publikationen . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11-2 Entity Lifecycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-19
A-1 Lieferanten Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-1
A-2 Personen Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-5
A-8
Literaturverzeichnis
[Che76] P.P. Chen. The entity-relationship model – towards a unified view of data. ACM
Transations on Datbase Systems, 1(1):9–36, 1976.
[Cod70] E.F. Codd. A relational model for large shared data banks. Communications of the
ACM, 13(6):9–36, Juni 1970.
[CY91] Peter Coad and Edward Yourdon. Object-Oriented Analysis. Yourdon Inc./PrenticeHall, Englewood Cliffs, 2 edition, 1991.
[EN07] R. Elmasri and S.B. Navathe. Fundamentals of Database Systems. Addison Wesley,
5 edition, 2007. ISBN 0-321-41506-X.
[HCF97] Graham Hamilton, Rick Cattell, and Maydene Fisher. JDBC™Database Access
with Java™A Tutorial and Annotated Reference. Addison Wesley, Reading, Massachusetts, 1997. ISBN 0-201-30995-5.
[Ull82] Jeffrey D. Ullman. Principles Of Database Systems. Pitman Publishing Limited,
Englewood Cliffs, 1982. ISBN.
[Vos00] G. Vossen. Datenmodelle, Datenbanksprachen und Datenbankmanagementsysteme.
Oldenburg Verlag, München Wien, 4 edition, 2000.
[Zeh89] C.A. Zehnder. Informationssysteme und Datenbanken. Teubner, Stuttgart, 1989.
ISBN 3-519-22480-1.
A-9
Inhaltsverzeichnis
1 Einführung
1-1
1.1
Filesysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-1
1.2
Definition Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-2
1.3
Eigenschaften einer Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-3
1.4
Die Architektur von Datenbanksystemen . . . . . . . . . . . . . . . . . . . . . . 1-4
1.5
Physische Datenunabhängigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-5
1.6
Der Datenbank Administrator (DBA) . . . . . . . . . . . . . . . . . . . . . . . 1-7
2 Das Entity-Relationship Modell
2-1
2.1
Datenmodellierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-1
2.2
Darstellung von Einzelfällen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-2
2.3
2.4
2.2.1
Entität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-2
2.2.2
Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-3
2.2.3
Faktum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-3
2.2.4
Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-3
Darstellung von mehreren Fällen . . . . . . . . . . . . . . . . . . . . . . . . . . 2-4
2.3.1
Die Entitätsmenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-5
2.3.2
Domäne oder Wertebereich . . . . . . . . . . . . . . . . . . . . . . . . . 2-6
2.3.3
Entitätsattribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-6
2.3.4
Entitätsschlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-8
2.3.5
Darstellung von Entitätsmengen und Entitätsattribute . . . . . . . . . . 2-9
2.3.6
Beziehungsmengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-10
2.3.7
Darstellung von Beziehungsmengen . . . . . . . . . . . . . . . . . . . . . 2-12
2.3.8
Beziehungsattribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-12
2.3.9
Darstellung von Beziehungsattributen . . . . . . . . . . . . . . . . . . . 2-13
Semantische Datenmodellierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-13
2.4.1
Generalisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-13
2.4.2
Darstellung von Generalisierungen . . . . . . . . . . . . . . . . . . . . . 2-14
2.4.3
Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-15
A-10
2.4.4
Darstellung von Aggregationen . . . . . . . . . . . . . . . . . . . . . . . 2-15
2.4.5
Vorgehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-15
2.4.6
Erkennen von Entitätsmengen
2.4.7
Erkennen von Beziehungsmengen . . . . . . . . . . . . . . . . . . . . . . 2-19
2.4.8
Semantische Datenmodellierung . . . . . . . . . . . . . . . . . . . . . . . 2-20
2.4.9
Erkennen von Attributen . . . . . . . . . . . . . . . . . . . . . . . . . . 2-20
. . . . . . . . . . . . . . . . . . . . . . . 2-16
2.4.10 Zeichnen des ERDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-21
2.5
Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-22
2.5.1
Fragen zur Theorie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-22
2.5.2
Verwalten von Computerliteratur . . . . . . . . . . . . . . . . . . . . . . 2-22
3 Das Relationenmodell
3-1
3.1
Attribute und Domänen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1
3.2
Relationenschema, Relation und Tupel . . . . . . . . . . . . . . . . . . . . . . . 3-3
3.3
Semantische Schlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-5
3.4
Syntaktische Schlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-6
3.5
Datenbankschema und Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . 3-7
3.6
Fremdschlüssel (foreign key) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-8
3.7
Objektidentität (Surrogate) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-10
3.8
Fremdschlüssel und Löschen von Tupel . . . . . . . . . . . . . . . . . . . . . . . 3-10
3.9
Notation für Relationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-11
3.9.1
Relationen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-11
3.9.2
Kandidatschlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-12
3.9.3
Fremdschlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-12
3.10 Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-13
4 Relationenalgebra
4-1
4.1
Eigenschaften der Relationenalgebra . . . . . . . . . . . . . . . . . . . . . . . . 4-1
4.2
Die Operatoren der Relationenalgebra . . . . . . . . . . . . . . . . . . . . . . . 4-2
4.3
4.2.1
Selektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-2
4.2.2
Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-3
4.2.3
Natürlicher Verbund . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-4
4.2.4
Mengenoperationen: Die Vereinigung und die Differenz . . . . . . . . . . 4-6
4.2.5
Umbenennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-7
Ausdrücke der Relationenalgebra . . . . . . . . . . . . . . . . . . . . . . . . . . 4-8
4.3.1
Abgeschlossenheit und Adequatheit . . . . . . . . . . . . . . . . . . . . . 4-8
4.3.2
Effiziente Implementierbarkeit . . . . . . . . . . . . . . . . . . . . . . . . 4-10
4.3.3
Optimierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-11
A-11
4.4
Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-14
4.4.1
Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-14
4.4.2
Theoretische Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-15
4.4.3
Stundenplan Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-15
5 Datenbank Sprache (SQL)
5.1
Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-1
5.1.1
5.2
5.3
5.4
5.5
5-1
Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-2
Datenmanipulationssprache (DML) . . . . . . . . . . . . . . . . . . . . . . . . . 5-2
5.2.1
Relationale Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-2
5.2.2
Operatoren und Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . 5-10
5.2.3
SQL Built-In Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . 5-10
5.2.4
Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-11
5.2.5
Die
5.2.6
Die GROUP BY Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-14
5.2.7
Die HAVING Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-15
5.2.8
Null-Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-15
ORDER BY
Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-14
Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-16
5.3.1
Definition von Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-16
5.3.2
Operationen auf Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-17
5.3.3
Verwendung von Views . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-21
Update und Delete Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-23
5.4.1
INSERT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-23
5.4.2
UPDATE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-23
5.4.3
DELETE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-24
Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-25
5.5.1
SQL-Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-25
5.5.2
Aufgaben zu Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-26
6 Normalisierung (Dekomposition)
6-1
6.1
Erste Normalform 1NF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-2
6.2
Update-Anomalien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-3
6.3
Funktionale Abhängigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-3
6.4
6.3.1
Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-3
6.3.2
Implikation von Funktionalen Abhängigkeiten . . . . . . . . . . . . . . . 6-4
6.3.3
Ableitung von funktionalen Abhängigkeiten . . . . . . . . . . . . . . . . 6-6
6.3.4
Anwendung des Membership-Algorithmus . . . . . . . . . . . . . . . . . 6-8
Zweite-, dritte- und Boyce-Codd-Normalform . . . . . . . . . . . . . . . . . . . 6-11
A-12
6.5
6.6
Dekomposition und Syntheseverfahren . . . . . . . . . . . . . . . . . . . . . . . 6-15
6.5.1
Dekomposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-17
6.5.2
Synthese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-19
Mehrwertige Abhängigkeiten und 4NF . . . . . . . . . . . . . . . . . . . . . . . 6-20
7 Vom ERM zum Datenbankschema
7.1
7.2
7.3
7.4
7-1
Datendefinitionssprache (SQL/DDL) . . . . . . . . . . . . . . . . . . . . . . . . 7-1
7.1.1
Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-1
7.1.2
Domänen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-2
7.1.3
Relationenschema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-3
7.1.4
Integritätsbedingungen (Constraint) . . . . . . . . . . . . . . . . . . . . 7-5
ERD und Datenbankschema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10
7.2.1
Entitätsmengen und Attributte . . . . . . . . . . . . . . . . . . . . . . . 7-10
7.2.2
Generalisierung und Spezialisierung . . . . . . . . . . . . . . . . . . . . 7-12
7.2.3
Beziehungsmengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-14
7.2.4
Beziehungsattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-16
7.2.5
Namenskonflikte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-18
7.2.6
Kontrolle der 3NF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-19
Ein vollständiges Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-22
7.3.1
Problembeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-22
7.3.2
Erstellen des Datenbankschemas . . . . . . . . . . . . . . . . . . . . . . 7-23
Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-26
8 Routinen und Trigger
8.1
8-1
Routinen (SQL-invoked routine) . . . . . . . . . . . . . . . . . . . . . . . . . . 8-1
8.1.1
SQL-Routinen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-2
8.1.2
Prozedurale Erweiterung von SQL . . . . . . . . . . . . . . . . . . . . . 8-4
8.2
Trigger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-10
8.3
Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-13
9 SQL in Programmiersprachen
9.1
9.2
9-1
Java Datenbankanbindung JDBC™
. . . . . . . . . . . . . . . . . . . . . . . . 9-1
9.1.1
Was ist JDBC™ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-2
9.1.2
Aufbau von JDBC™ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-2
9.1.3
Programmieren mit JDBC™ . . . . . . . . . . . . . . . . . . . . . . . . 9-6
9.1.4
SQL-ROUTINEN und JDBC . . . . . . . . . . . . . . . . . . . . . . . . 9-11
9.1.5
Meta-Informationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-14
Embedded SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-17
A-13
9.3
9.2.1
Syntax von embedded SQL-Statements
. . . . . . . . . . . . . . . . . . 9-17
9.2.2
Aufbau eines C-Programms mit embedded SQL . . . . . . . . . . . . . . 9-18
9.2.3
Cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-21
Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-28
9.3.1
Arbeiten mit JDBC™ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-28
9.3.2
JDBC™Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-28
10 Datenintegrität (Transaktionstheorie)
10-1
10.1 Konsistenz einer Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1
10.2 Das Transaktionsmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-2
10.3 Atomarität und Dauerhaftigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-3
10.3.1 Das Logfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-3
10.3.2 Checkpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-3
10.3.3 Recovery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-4
10.4 Zwei Phasen Commit (two-phase commit) . . . . . . . . . . . . . . . . . . . . . 10-5
10.4.1 Koordinator Timeouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-5
10.4.2 Timeout in den beteiligten Knoten . . . . . . . . . . . . . . . . . . . . . 10-6
10.4.3 Koordinator und Recovery . . . . . . . . . . . . . . . . . . . . . . . . . . 10-7
10.4.4 Beteiligte Knoten und Recovery
. . . . . . . . . . . . . . . . . . . . . . 10-7
10.5 Isolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-7
10.5.1 Verlorene Updates (lost Update) . . . . . . . . . . . . . . . . . . . . . . 10-8
10.5.2 Uncommitted Dependency . . . . . . . . . . . . . . . . . . . . . . . . . . 10-8
10.5.3 Inkonsitente Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-8
10.5.4 Locking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-9
10.5.5 Behandlung der Deadlocks
. . . . . . . . . . . . . . . . . . . . . . . . . 10-11
10.6 Transaktionskontrolle ohne Locking . . . . . . . . . . . . . . . . . . . . . . . . . 10-12
10.6.1 Optimistische Transaktionskontrolle . . . . . . . . . . . . . . . . . . . . 10-12
10.6.2 Zeitstempel (Timestamp) Methode . . . . . . . . . . . . . . . . . . . . . 10-12
10.7 Transactionen und SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-13
10.7.1 Sart einer Transaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-13
10.7.2 Ende einer Transaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-13
10.7.3 Eigenschaften einer Transaktion . . . . . . . . . . . . . . . . . . . . . . 10-14
10.8 Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-16
10.8.1 Theoretische Fragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-16
10.8.2 Praktische Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-16
A-14
11 Object Persistenz in Java
11-1
11.1 Persistenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-1
11.1.1 Definition der Persistenz . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-1
11.1.2 Techniken zur Realisierung der Persistenz . . . . . . . . . . . . . . . . . 11-2
11.1.3 Objektrelationales Mapping . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2 Java Persistence API (JPA) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.1 Vorgehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.2 Mapping mit JPA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.3 Programmieren mit JPA . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-17
11.2.4 Lifecycle eines Objektes . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-19
11.2.5 Callback Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-21
11.2.6 Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-23
11.3 Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-27
12 Security (Datenschutz)
12-1
12.1 Schutz vor unberechtigtem Zugriff . . . . . . . . . . . . . . . . . . . . . . . . . 12-1
12.1.1 Benutzer
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-2
12.1.2 Datenschutz und SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-2
12.1.3 Datenschutz und Views . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-4
12.1.4 Netzwerke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-5
12.2 Persönlichkeitsschutz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-5
A Beispiele
A-1
A.1 Lieferantenbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-1
A.1.1 Lieferanten ERD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-1
A.1.2 Darenbankschema Lieferanten . . . . . . . . . . . . . . . . . . . . . . . . A-2
A.1.3 Daten zur Lieferantendatenbank . . . . . . . . . . . . . . . . . . . . . . A-2
A.2 Personenbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-5
A.2.1 Personen ERD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-5
A.2.2 Datenbankschema Personen . . . . . . . . . . . . . . . . . . . . . . . . . A-5
A.2.3 Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A-5
Abbildungsverzeichnis
A-7
Literaturverzeichnis
A-9
Inhaltsverzeichnis
A-10
A-15
Herunterladen