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