Exceptionfault
Erfahrenes Mitglied
Ein wunderschönes Feature, welches man als Anwendungsentwickler in Verbindung mit Datenbanken unbedingt kennen sollte lautet "Objektrelationales Datenbankdesign". Ich muss gestehen, dass ich nicht beurteilen kann, wie gut die Möglichkeiten in anderen DBM Systemen wie DB2 oder MS-SQL Server umgesetzt sind. In Oracle bieten sich jedoch meiner Meinung nach wunderbare Möglichkeiten, welche viel zu selten eingesetzt werden.
Zunächst eine kleine Erläuterung der Unterschiede zwischen "normalen" relationalen Datenbankmodellen und "objektrelationalen Datenbankmodellen". Die meisten DB Benutzer kennen, wenn sie z.B. mySQL verwenden lediglich zweidimensionale Tabellen. Diese werden im Idealfall mit sog. Constraints verknüpft um eine logische Datenintegrität zu erhalten. In einer relationalen Tabelle spiegelt jede Spalte ein einfaches Attribut, z.b. ein Datum wieder, und jede Zeile unterschiedliche Ausprägungen.
Nehmen wir zum einfacheren Verständnis eine kleines Beispiel mit Kundendaten. Wir haben eine Kundentabelle mit Kundennummer und Name. Da Kunden möglicherweise mehrere Kontaktadressen haben, müssen wir eine eigene Tabelle mit Adressen anlegen und per Kundennummer darauf verweisen. Weiter gibt es eine Tabelle Rechnungen, sowie eine Tabelle Rechnungspositionen. Ich lasse in diesem Beispiel bewusst Primary- und Foreign Keys weg um den Code kürzer zu halten.
Stellen wir uns nun vor, wir möchten eine Auswertung von verkauften Artikeln in Beziehung unseren Kunden durchführen. Allein dafür bräuchten wir einen JOIN auf 4 Tabellen. Das Statement sähe nicht sonderlich hübsch aus, erst recht nicht wenn noch Einschränkungen z.B. nach Postleitzahl Regionen hinzukämen. Schauen wir uns das gleiche Schema als Objektrelationales Modell einmal an. Ziel ist es hier nicht in "flachen, zweidimensionalen" Tabellen, sondern in Objekten zu denken und einen realen Zustand bestmöglich in der Datenbank abzubilden.
In unserem Beispiel bedeutet dies: Wir haben Kunden und Rechnungen. Jeder Kunde kann mehrere Adressen haben. Jede Rechnung hat einzelne Rechnungspositionen und einen eindeutigen Kunden. Um eine 1:n Beziehung abzubilden, z.B. die Adressen der Kunden, brauchen wir eine sog. "Nested Tables". Man kann es sich so vorstellen, dass in der Zelle der Kundentabelle nun eine eigene Tabelle angelegt wird, welche die Adressen des jeweiligen Kunden enthalten. Ebenso verhält es sich mit den Rechnungen und den Rechnungspositionen.
Aber zunächst benötigen wir die Definitionen unserer Objekte:
Anhand der Endungen kann man leicht erkennen um welchen Typ es sich bei den Objekten handelt. "_t" steht für eine Typdefinition und "_nt" für Nested Table. "REF" im Typ Rechnung bedeutet Reference und ist quasi wie ein Link auf unseren Kunden. Bis jetzt haben wir nur Typdefinitionen, aber noch nichts worin wir die Objeke auch speichern können. Dazu legen wir nun zwei Tabellen an. Zu jeder Tabelle müssen wir auch definieren wie bzw. unter welchem Namen die Nested Tables gespeichert werden.
Fügen wir nun ein paar Testdaten ein:
Der Kunde Heinz Mustermann hat also zwei Adressen, und eine Rechnung mit 3 Artikeln welche zur Adresse Nr. 2 bestellt wurden.
Die Insert Statements sehen so seltsam aus, da wir mehrere Tabellen (NT) pro Insert Statement befüllen und außerdem ja nun nicht mehr mit einfachen Datentypen pro Zelle arbeiten, sondern mit Objekten. Insofern sieht das ganze zugegebenermassen etwas komplizierter aus. Es ist auch mehr Code in der Entwicklung der Objekte. Kommen wir nun aber zu der schönen Seite: Den Abfragen!
Wir möchten alle Anschriften von Kunde Nr 1. Hierfür müssen wir in der From Klauses lediglich die Spalte ADRESSES als Tabelle deklarieren.
Sicher, das wäre auch mit einem JOIN noch recht übersichtlich. Jetzt möchten wir alle Artikel und Rechnungsnummern, mit der zugehörigen Lieferadresse und Anrede aller Kunden.
Und alles klar ? ;-)
Und nun das wirklich tolle. Bisher haben wir trotz der Objekte unsere Statements immer so aufgebaut, dass wir eine zweidimensionale Tabelle zurückbekommen. Warum also der Aufwand mit den Objekten ?
Ganz einfach, weil ich mir in meinem Programm auch direkt die Objekte abrufen kann. Und das gilt nicht nur für die Werte des jeweiligen Objektes. Ich kann sogar Proceduren zu einem Objekt anlegen und ausführen. Da dies hier viel zu weit führen würde nur ein Beispiel, schnell in VBA (Excel) zusammengebastelt:
Das ganze ist ein ziemlich umfangreiches Thema und ich habe hier viele wichtige Aspekte unterschlagen (müssen). Hoffe aber, dass ich trotzdem einen kleine Einblick in die Möglichkeiten geben konnte.
Zunächst eine kleine Erläuterung der Unterschiede zwischen "normalen" relationalen Datenbankmodellen und "objektrelationalen Datenbankmodellen". Die meisten DB Benutzer kennen, wenn sie z.B. mySQL verwenden lediglich zweidimensionale Tabellen. Diese werden im Idealfall mit sog. Constraints verknüpft um eine logische Datenintegrität zu erhalten. In einer relationalen Tabelle spiegelt jede Spalte ein einfaches Attribut, z.b. ein Datum wieder, und jede Zeile unterschiedliche Ausprägungen.
Nehmen wir zum einfacheren Verständnis eine kleines Beispiel mit Kundendaten. Wir haben eine Kundentabelle mit Kundennummer und Name. Da Kunden möglicherweise mehrere Kontaktadressen haben, müssen wir eine eigene Tabelle mit Adressen anlegen und per Kundennummer darauf verweisen. Weiter gibt es eine Tabelle Rechnungen, sowie eine Tabelle Rechnungspositionen. Ich lasse in diesem Beispiel bewusst Primary- und Foreign Keys weg um den Code kürzer zu halten.
Code:
CREATE TABLE customer (
CUSTID NUMBER(10,0),
FIRSTNAME VARCHAR2(30),
LASTNAME VARCHAR2(30)
)
/
CREATE TABLE addresses (
ADDID NUMBER(10,0),
CUSTID NUMBER(10,0),
STREET VARCHAR2(40),
ZIP VARCHAR2(5),
CITY VARCHAR2(40)
)
/
CREATE TABLE invoice (
INVOICEID NUMBER(10,0),
ADDID NUMBER(10,0),
PURCHDATE DATE
)
/
CREATE TABLE artsales (
ARTID NUMBER(10,0),
INVOICEID NUMBER(10,0),
AMOUNT NUMBER(10,0)
)
/
In unserem Beispiel bedeutet dies: Wir haben Kunden und Rechnungen. Jeder Kunde kann mehrere Adressen haben. Jede Rechnung hat einzelne Rechnungspositionen und einen eindeutigen Kunden. Um eine 1:n Beziehung abzubilden, z.B. die Adressen der Kunden, brauchen wir eine sog. "Nested Tables". Man kann es sich so vorstellen, dass in der Zelle der Kundentabelle nun eine eigene Tabelle angelegt wird, welche die Adressen des jeweiligen Kunden enthalten. Ebenso verhält es sich mit den Rechnungen und den Rechnungspositionen.
Aber zunächst benötigen wir die Definitionen unserer Objekte:
Code:
CREATE OR REPLACE TYPE address_t AS OBJECT (
ADDID NUMBER(10,0),
STREET VARCHAR2(40),
ZIP VARCHAR2(5),
CITY VARCHAR2(40)
)
/
CREATE OR REPLACE TYPE addresses_nt AS TABLE OF address_t
/
CREATE OR REPLACE TYPE invoicepos_t AS OBJECT (
ARTID NUMBER(10,0),
AMOUNT NUMBER(10,0)
)
/
CREATE OR REPLACE TYPE invoicepos_nt AS TABLE OF invoicepos_t
/
CREATE OR REPLACE TYPE customer_t AS OBJECT (
CUSID NUMBER(10,0),
FIRSTNAME VARCHAR2(30),
LASTNAME VARCHAR2(30),
ADDRESSES addresses_nt
)
/
CREATE OR REPLACE TYPE invoice_t AS OBJECT (
INVOICEID NUMBER(10,0),
CUSTOMER REF customer_t,
ADDID NUMBER(10,0),
POSITIONS invoicepos_nt
)
/
Anhand der Endungen kann man leicht erkennen um welchen Typ es sich bei den Objekten handelt. "_t" steht für eine Typdefinition und "_nt" für Nested Table. "REF" im Typ Rechnung bedeutet Reference und ist quasi wie ein Link auf unseren Kunden. Bis jetzt haben wir nur Typdefinitionen, aber noch nichts worin wir die Objeke auch speichern können. Dazu legen wir nun zwei Tabellen an. Zu jeder Tabelle müssen wir auch definieren wie bzw. unter welchem Namen die Nested Tables gespeichert werden.
Code:
CREATE TABLE customer OF customer_t
NESTED TABLE ADDRESSES STORE AS all_adresses RETURN AS VALUE
/
CREATE TABLE invoices OF invoice_t
NESTED TABLE POSITIONS STORE AS all_invoicepos RETURN AS VALUE
/
Fügen wir nun ein paar Testdaten ein:
Code:
INSERT INTO CUSTOMER VALUES ( 1, 'Heinz', 'Mustermann',
ADDRESSES_NT (
ADDRESS_T(1, 'Musterstr. 25', '01234', 'Musterdorf'),
ADDRESS_T(2, 'Musterdorfstr. 11b', '98765', 'Musterstadt')
)
);
INSERT INTO INVOICES VALUES ( 1,
(SELECT REF(C) FROM CUSTOMER C WHERE CUSID = 1),
2,
INVOICEPOS_NT(
INVOICEPOS_T( 345, 1 ),
INVOICEPOS_T( 123, 2 ),
INVOICEPOS_T( 789, 4 )
)
);
Die Insert Statements sehen so seltsam aus, da wir mehrere Tabellen (NT) pro Insert Statement befüllen und außerdem ja nun nicht mehr mit einfachen Datentypen pro Zelle arbeiten, sondern mit Objekten. Insofern sieht das ganze zugegebenermassen etwas komplizierter aus. Es ist auch mehr Code in der Entwicklung der Objekte. Kommen wir nun aber zu der schönen Seite: Den Abfragen!
Wir möchten alle Anschriften von Kunde Nr 1. Hierfür müssen wir in der From Klauses lediglich die Spalte ADRESSES als Tabelle deklarieren.
Code:
SELECT FIRSTNAME, LASTNAME, STREET, ZIP, CITY FROM CUSTOMER U, TABLE(U.ADDRESSES) WHERE CUSID = 1;
LASTNAME FIRSTNAME STREET ZIP CITY
----------------------------- ------------------------------ ---------------------------------------- ----- -----------
Mustermann Heinz Musterstr. 25 01234 Musterdorf
Mustermann Heinz Musterdorfstr. 11b 98765 Musterstadt
Sicher, das wäre auch mit einem JOIN noch recht übersichtlich. Jetzt möchten wir alle Artikel und Rechnungsnummern, mit der zugehörigen Lieferadresse und Anrede aller Kunden.
Code:
SELECT VALUE(I).CUSTOMER.LASTNAME,
VALUE(I).CUSTOMER.FIRSTNAME,
A.STREET,
P.ARTID,
I.INVOICEID,
P.AMOUNT
FROM INVOICES I, TABLE(I.POSITIONS) P, TABLE(VALUE(I).CUSTOMER.ADDRESSES) A
WHERE A.ADDID =I.ADDID;
VALUE(I).CUSTOMER.LASTNAME VALUE(I).CUSTOMER.FIRSTNAME STREET ARTID AMOUNT
------------------------------ ------------------------------ ---------------------------------------- ---------- ----------
Mustermann Heinz Musterdorfstr. 11b 345 1
Mustermann Heinz Musterdorfstr. 11b 123 2
Mustermann Heinz Musterdorfstr. 11b 789 4
Und alles klar ? ;-)
Und nun das wirklich tolle. Bisher haben wir trotz der Objekte unsere Statements immer so aufgebaut, dass wir eine zweidimensionale Tabelle zurückbekommen. Warum also der Aufwand mit den Objekten ?
Ganz einfach, weil ich mir in meinem Programm auch direkt die Objekte abrufen kann. Und das gilt nicht nur für die Werte des jeweiligen Objektes. Ich kann sogar Proceduren zu einem Objekt anlegen und ausführen. Da dies hier viel zu weit führen würde nur ein Beispiel, schnell in VBA (Excel) zusammengebastelt:
Code:
Set result = db.CreateDynaset("select value(c) cust from customers c", 0&)
Set customer = result("cust").Value
Set addr_nt = customer.addresses
Debug.Print customer.Lastname
Debug.Print customer.Firstname
For i = 1 To addr_nt.TableSize
Set addr = addr_nt(i)
Debug.Print addr.city & " " & contact.street
addr.IterNext
Next i
Das ganze ist ein ziemlich umfangreiches Thema und ich habe hier viele wichtige Aspekte unterschlagen (müssen). Hoffe aber, dass ich trotzdem einen kleine Einblick in die Möglichkeiten geben konnte.