Bei Flavia IT legen wir Wert darauf, nicht nur aktuelle Technologien, sondern auch die historische Entwicklung der IT zu verstehen.
Dieser Vortrag beleuchtet ein spannendes Kapitel aus der Frühzeit datengetriebener Webanwendungen.
Hintergrund
Vor Kurzem durfte ich bei Flavia IT einen Vortrag über ein spannendes Thema aus meiner Anfangszeit in der IT halten: Wie wir vor rund 20 Jahren Webanwendungen gebaut haben – und zwar mit sehr viel Logik direkt in der Datenbank.
Damals war ich noch Auszubildender, und was ich in diesem Projekt erlebte, unterschied sich deutlich von dem, was man offiziell über sauberes Datenbankdesign lernte.
Während in der Berufsschule und in Lehrbüchern die strikte Normalisierung, Trennung von Logik und Datenhaltung und klare Schichtenarchitekturen gepredigt wurden, sah die Realität im Projekt ganz anders aus – und das war zunächst ziemlich verwirrend für mich.
Statt Datenbanklogik zu vermeiden, wurde sie hier bewusst genutzt:
- Komplexe Logik wurde direkt in PL/SQL-Prozeduren geschrieben, die gleichzeitig HTML-Templates erzeugten.
- Denormalisierte Tabellen wurden bewusst aufgebaut, um Drilldowns ohne Joins zu ermöglichen.
- Alle Daten kamen im ersten Schritt als VARCHAR2 in die Datenbank – selbst Zahlen oder Datumswerte.
Der Grund dafür war einfach: Es ging um gigantische Datenmengen – Terabytes an Rezeptdaten aus ganz Deutschland. Ziel war es, die Daten für Analysezwecke performant aufzubereiten. Nachdem die Daten erfolgreich eingelesen wurden, wurden entsprechende Datentypen gesetzt und es wurden Qualitätsanalysen gemacht.
In der Ausbildung arbeitet man typischerweise an viel kleineren Datensätzen, wo saubere Strukturierung wichtiger als rohe Geschwindigkeit ist.
Hier lernte ich: Wenn die Datenmengen riesig werden, gelten andere Regeln.
Performance geht dann manchmal vor akademischer Reinheit.
Technologischer Hintergrund
Vor 20 Jahren standen uns keine modernen Web-Frameworks wie Angular, Vue oder React zur Verfügung.
JQuery steckte noch in den Kinderschuhen, und selbst Bootstrap war noch neu.
Serverseitig setzte man auf Oracle-Datenbanken und PL/SQL Stored Procedures.
Die Frontends wurden größtenteils mit einfachem HTML, CSS und wenig JavaScript gebaut.
Der Anwendungsfall
Es ging um die Verwaltung und Analyse aller Patientenrezepte in Deutschland:
- Welche Medikamente verschreiben Ärzte am häufigsten?
- Welche Hersteller dominieren bestimmte Wirkstoffgruppen?
- Wie können diese Daten für die Weiterentwicklung von Abrechnungsmodellen genutzt werden?
Datenmodell und Aufbau
Das zugrundeliegende Datenmodell umfasste:
- Hersteller
- Ärzte
- Patienten
- Präparate
- Rezepte
Besondere Struktur:
- Eine zentrale „angereicherte“ Tabelle, in der Daten aus verschiedenen Stammdatenquellen zusammengeführt wurden
- Drilldown-Tabellen, die speziell für einzelne Analysen aufgebaut wurden
- Fokus auf Performance statt auf perfekte Normalisierung
Interessante Aspekte
- HTML-Templates direkt aus der Datenbank generiert
- Vollständige Entkopplung von Darstellung und Analyse-Tabellen
- Starke Logik in der Datenbank, schwache Logik auf Anwendungsebene
- Große Datenmengen (gigantische CSV-Exporte im zweistelligen GB-Bereich)
Rückblickend
Die Erfahrungen aus diesem Projekt haben meine Sicht auf Softwarearchitektur stark geprägt.
Heute setzen wir meist auf klar getrennte Architekturen mit Microservices, REST-APIs und modernen Frontends – aber die Grundidee bleibt: Die Architektur muss immer zur Problemstellung passen.
Wenn die Datenmengen extrem groß sind, können pragmatische Lösungen manchmal besser funktionieren als theoretisch „perfekte“ Architekturen.
Von der Theorie zur Praxis: Wie wurde das technisch gelöst?
Nachdem wir nun die Herausforderungen und Besonderheiten des Projekts kennengelernt haben, stellt sich die Frage:
Wie setzt man solche Anforderungen technisch um, wenn Performance und Datenmenge im Vordergrund stehen?
Statt auf klassische, stark normalisierte Datenmodelle und komplexe Abfragen mit vielen Joins zu setzen, wurde ein anderer Weg gewählt:
- Daten wurden gezielt voraggregiert und in speziell vorbereiteten Tabellen abgelegt.
- PL/SQL-Prozeduren in der Datenbank übernahmen nicht nur die Datenverarbeitung, sondern auch die Generierung der HTML-Ausgabe für das Web-Frontend.
- Dadurch konnten auch große Datenmengen sehr schnell und ressourcenschonend ausgewertet und dargestellt werden.
Im Folgenden zeige ich, wie diese technische Lösung konkret aussah – am Beispiel der sogenannten Drilldown-Tabellen und der dynamischen HTML-Generierung mit PL/SQL.
Drilldown-Tabellen und PL/SQL-Generierung
Drilldown: Aggregierte Tabelle
Um schnelle Auswertungen und gezielte Drilldowns zu ermöglichen, wurde unter anderem eine speziell vorberechnete Tabelle arzt_patient_hersteller
angelegt:
CREATE TABLE arzt_patient_hersteller AS
SELECT
ra.ArztName AS Arzt,
ra.ArztID AS ArztID,
ra.PZN AS PZN,
ra.PZNName AS PZNName,
ra.HerstellerName AS Hersteller,
ra.HerstellerID AS HerstellerID,
ra.PatName AS Patient,
ra.PatientID AS PatientID,
COUNT(DISTINCT ra.RezeptID) AS Anzahl_Rezepte
FROM rezepte_angereichert ra
GROUP BY
ra.ArztName,
ra.ArztID,
ra.PZN,
ra.PZNName,
ra.HerstellerName,
ra.HerstellerID,
ra.PatName,
ra.PatientID;
Zweck der Tabelle
Die Tabelle arzt_patient_hersteller
dient dazu, besonders schnelle Zugriffe auf aggregierte Rezeptdaten zu ermöglichen. Sie erlaubt es, für eine bestimmte Kombination aus Arzt, Patient und Hersteller alle relevanten Präparate sowie die jeweilige Anzahl der ausgestellten Rezepte auf einen Blick zu ermitteln. Da die Daten bereits vorab zusammengefasst und in dieser Tabelle abgelegt wurden, sind zur Laufzeit keine aufwendigen Joins mehr nötig. Das sorgt für eine deutlich bessere Performance bei Drilldown-Analysen und ermöglicht eine effiziente Auswertung auch sehr großer Datenmengen.
Die Voraggregation der Daten in dieser Tabelle nahm etwa einen Tag in Anspruch – ein einmaliger, aber notwendiger Schritt, um die riesigen Datenmengen effizient aufzubereiten. Der große Vorteil: Die spätere Darstellung und das Rendering der Ergebnisse im Web-Frontend erfolgten anschließend nahezu verzögerungsfrei und in Echtzeit.
PL/SQL-Prozedur: HTML Drilldown-View
Die Daten aus der arzt_patient_hersteller
-Tabelle wurden über eine PL/SQL-Prozedur direkt als HTML-Tabelle ausgegeben:
CREATE OR REPLACE PROCEDURE get_arzt_patient_hersteller(
p_arzt_id IN NUMBER,
p_patient_id IN NUMBER,
p_hersteller_id IN NUMBER
) IS
BEGIN
-- HTML-Header
header();
-- Schleife über die arzt_patient_hersteller-Tabelle
FOR rec IN (
SELECT ArztID, PatientID, HerstellerID, PZN, PZNName, Anzahl_Rezepte
FROM arzt_patient_hersteller
WHERE
ArztID = p_arzt_id
AND PatientID = p_patient_id
AND HerstellerID = p_hersteller_id
) LOOP
-- HTML-Table Rendering
HTP.P('<tr>');
HTP.P('<td>' || rec.PZN || '</td>');
HTP.P('<td>' || rec.PZNName || '</td>');
-- Link auf weitere Details
HTP.P('<td><a href="praeparat_arzt_patient_hersteller_view?p_arzt_id=' || p_arzt_id ||
'&p_patient_id=' || p_patient_id ||
'&p_hersteller_id=' || p_hersteller_id ||
'&p_pzn=' || rec.PZN || '">' || rec.Anzahl_Rezepte || '</a></td>');
HTP.P('</tr>');
END LOOP;
-- HTML-Footer
footer();
END get_arzt_patient_hersteller;
Was passiert hier genau?
- Benutzer wählt: Arzt, Patient, Hersteller.
- Die Prozedur erzeugt eine Tabellenansicht aller verschriebenen Präparate.
- Die Rezeptanzahl wird als klickbarer Link dargestellt ➔ führt zu einem noch tieferen Drilldown auf Ebene einzelner Rezepte.
Exkurs: Der (+)
Operator in Oracle – Outer Joins verstehen
Was ist der (+)
Operator?
Der (+)
Operator ist eine Besonderheit der Oracle SQL-Syntax und wird verwendet, um sogenannte Outer Joins (insbesondere Left Outer Joins) zu realisieren. Während moderne SQL-Standards mit den Schlüsselwörtern LEFT OUTER JOIN
, RIGHT OUTER JOIN
und FULL OUTER JOIN
arbeiten, nutzte Oracle bis Version 9i (und in vielen Altprojekten noch heute) diese spezielle Klammer-Notation.
Wie funktioniert der (+)
Operator?
Der (+)
Operator wird im WHERE-Teil einer SQL-Abfrage an die Spalte der Tabelle gesetzt, aus der auch dann Daten angezeigt werden sollen, wenn es keine passende Zeile in der verknüpften Tabelle gibt. Das ist typisch für einen Left Outer Join.
Beispiel:
SELECT
r.RezeptID,
pat.Name AS patient_name
FROM
Rezepte r,
Patienten pat
WHERE
pat.PatientID = r.PatientID(+);
Bedeutung:
Zeige alle Patienten an – auch dann, wenn sie kein Rezept haben. Für Patienten ohne Rezept bleibt das Feld RezeptID
leer (NULL).
Typische Fehlerquellen
- Richtung beachten: Das
(+)
steht immer auf der Seite, die „optional“ ist – also dort, wo fehlende Werte erlaubt sind. - Nicht kombinierbar mit JOIN-Syntax: Der
(+)
Operator funktioniert nur mit der alten, komma-separierten FROM-Notation, nicht mit der modernen JOIN-Notation. - Nur einseitig pro Bedingung: Pro Vergleich darf das
(+)
nur auf einer Seite stehen, sonst gibt es einen Fehler.
Moderne Alternative
Heutzutage wird der (+)
Operator als veraltet angesehen. Die ANSI-SQL-Syntax ist klarer und portabler:
SELECT
r.RezeptID,
pat.Name AS patient_name
FROM
Patienten pat
LEFT OUTER JOIN Rezepte r ON pat.PatientID = r.PatientID;
Vorteile:
- Besser lesbar
- Standardisiert (funktioniert in allen modernen Datenbanken)
- Flexibler für komplexe Joins
Weitere Varianten
- Right Outer Join:
Das(+)
kann auch auf der anderen Seite stehen, um einen Right Outer Join zu simulieren.
Beispiel:WHERE pat.PatientID(+) = r.PatientID
- Full Outer Join:
Mit dem(+)
Operator ist ein Full Outer Join nicht direkt möglich. Hierfür muss die ANSI-Syntax verwendet werden.
Zusammenfassung
- Der
(+)
Operator ist ein Oracle-spezifischer Weg, Outer Joins zu schreiben. - Er ist hilfreich, um auch Datensätze ohne Entsprechung in der verknüpften Tabelle anzuzeigen.
- Für neue Projekte sollte die ANSI-SQL JOIN-Syntax bevorzugt werden.
Tipp:
Wenn du alte Oracle-Projekte wartest, wirst du dem (+)
Operator noch oft begegnen. Für neue Entwicklungen empfiehlt sich aber die moderne JOIN-Syntax – sie ist zukunftssicher!
Fazit
- Performante Aggregation der wichtigsten Daten durch
arzt_patient_hersteller
. - Dynamische HTML-Generierung durch PL/SQL-Prozeduren ➔ spart Webserver-Ressourcen.
- Komplette Drilldown-Navigation nur auf Basis von vorberechneten Tabellen und PL/SQL.
- Ich persönlich mochte den (+)-Operator sehr