Einführung in SQL: Prozeduren
Aus Wikibooks
Eine Prozedur – gespeicherte Prozedur, engl. StoredProcedure (SP) – ist vorgesehen für Arbeitsabläufe, die "immer wiederkehrende" Arbeiten direkt innerhalb der Datenbank ausführen sollen.
| Hinweis an Autoren betr. SQL-Dialekte Die Beispiele sollten auch für MS-SQL, MySql, Oracle verfasst werden. Je nach Bedarf und Interesse kann eines der vorhandenen Beispiele umgeschrieben oder wie unter Programmierung mit Vorlage:Navigationsleiste zum Ausklappen hinzugefügt werden. Ich Juetho habe auch nichts dagegen, wenn jemand mir einen Code-Vorschlag auf meine Diskussionsseite schreibt oder per Email schickt mit der Bitte, dass ich es einbauen möge. – Nach Erledigung durch einen Fachmann kann der Hinweis auf einen einzelnen Dialekt hier entfernt werden; nach Erledigung für alle DBMS kann der Hinweis insgesamt gelöscht werden. |
Inhaltsverzeichnis |
[Bearbeiten] Ein Überblick
Zur Verwendung von Prozeduren gibt es zwei gegensätzliche Standpunkte, die mit den Begriffen Fat Server und
Fat Client zusammengefasst werden können:
- Beim Fat Server wird so viel Funktionalität wie möglich in die Datenbank gelegt.
- Beim Fat Client ist die Datenbank nur für die Speicherung der Daten vorgesehen; sämtliche Verarbeitung erfolgt auf dem Arbeitsplatzrechner.
Der große Vorteil eines Fat Server liegt darin, dass Daten direkt in der Datenbank verarbeitet werden können, sofern keine Ansicht der Daten auf dem Arbeitsplatz benötigt wird. Es ist schließlich überflüssig, die Daten zuerst von der Datenbank zum Arbeitsplatz zu kopieren, dann automatisch zu verarbeiten und das Arbeitsergebnis (ohne manuelle Überprüfung) schließlich auf die Datenbank zurückzukopieren. Einfacher ist es, alles in der Datenbank ausführen zu lassen.
Der große Vorteil eines Fat Client liegt darin, dass das Client-Programm meistens schneller und einfacher geändert werden kann als die Arbeitsabläufe innerhalb der Datenbank.
In der Praxis ist eine Mischung beider Verfahren am sinnvollsten. Zur Steuerung der Arbeitsabläufe in der Datenbank werden die gespeicherten Prozeduren verwendet; Beispiele dafür werden in diesem Abschnitt behandelt.
[Bearbeiten] Prozedur definieren
[Bearbeiten] Prozedur erstellen
Die Syntax für die Definition einer Prozedur sieht so aus:
CREATE OR ALTER PROCEDURE <routine-name>
( [ <input-parameterliste> ] )
[ RETURNS ( <output-parameterliste> ) ]
AS
[ <variablenliste> ]
BEGIN
<routine body>
END
Notwendig sind folgende Angaben:
- neben dem Befehlsnamen der Name der Prozedur
- das Schlüsselwort AS als Zeichen für den Inhalt
- die Schlüsselwörter BEGIN und END als Begrenzer für den Inhalt
Hinzu kommen die folgenden Angaben:
- eine Liste von Eingabe-Parametern, sofern Werte übergeben werden sollen
- eine Liste von Ausgabe-Parametern, sofern Werte abgefragt werden sollen
- eine Liste von Variablen, die innerhalb der Prozedur verwendet werden
- die Befehle, die innerhalb der Prozedur ausgeführt werden sollen
Bei den Befehlen innerhalb der Prozedur handelt es sich zum Teil um "normale" SQL-Befehle und vor allem um Bestandteile der SQL-Programmiersprache. Zum Semikolon, das den Abschluss des CREATE-Befehls darstellen sollte, aber innerhalb des Inhalts bereits für jede einzelne Anweisung benutzt wird, beachten Sie bitte auch die Hinweise unter Anweisungen begrenzen.
[Bearbeiten] Prozedur ausführen
Es gibt mehrere Verfahren, wie eine Prozedur vom Nutzer, aus einem Anwendungsprogramm oder aus einer anderen Prozedur oder einem Trigger heraus benutzt werden kann.
Der direkte Weg geht über Execute Procedure und führt eine Prozedur aus:
EXECUTE PROCEDURE <routine-name> [ <eingabe-parameter> ]
[ RETURNING_VALUES <ausgabe-parameter> ] ;
Dem Namen der Prozedur folgen (soweit erforderlich) die Eingabe-Parameter; mehrere werden mit Komma getrennt. Sofern die Prozedur Werte zurückliefert, werden sie in eine oder mehrere Variablen eingetragen, die dem Begriff RETURNING_VALUES folgen.
Eine Prozedur kann auch mit Select Procedure wie eine Abfrage verwendet werden. Dies ist vor allem dann erforderlich, wenn eine Reihe von Werten oder Datensätzen zurückgeliefert werden.
SELECT * FROM <routine-name> [ <eingabe-parameter> ] ;
Die Ausgabe-Parameter stehen dann in der Liste der Spalten, also in der Ergebnismenge des SELECT-Befehls.
Man könnte auch an Execute Block denken, aber dieser Hinweis passt überhaupt nicht hierher. Ein Block ist eine Menge von Anweisungen, die einmalig benutzt werden, aber eben nicht in der Datenbank gespeichert werden. Ein solcher Block ersetzt eine gespeicherte Prozedur, wenn das Speichern überflüssig ist, und wird unter Programmierung: Routinen ohne feste Speicherung beschrieben.
[Bearbeiten] Prozedur ändern
Eine Änderung einer Prozedur ist nur möglich durch eine vollständige Neudefinition mit allen Bestandteilen. Deshalb wird das heutzutage fast immer durch CREATE OR ALTER zusammengefasst. Wenn dies nicht möglich ist, dann muss ein einzelner ALTER-Befehl benutzt werden, der alle Bestandteile der obigen Erläuterungen enthält.
[Bearbeiten] Prozedur löschen
Das geht wie üblich mit dem DROP-Befehl:
DROP PROCEDURE <routine-name>;
[Bearbeiten] Beispiele
| Hinweis Wir empfehlen, dieses Kapitel sowie das Kapitel Programmierung in zwei Fenstern Ihres Browsers gleichzeitig zu öffnen und diese nebeneinander zu setzen. Dann können Sie ein umfangreiches Beispiel sowie die passenden Einzelheiten je nach SQL-Dialekt gleichzeitig sehen. |
[Bearbeiten] Ersatz für eine View mit Parametern
Unter Views: Eine View mit variabler Selektion konnten variable Bedingungen nicht in eine View eingebunden werden; stattdessen wurde auf die Möglichkeit einer Prozedur hingewiesen.
Mache aus der View Mitarbeiter_in_Abteilung eine Prozedur Mitarbeiter_aus_Abteilung, die die Abteilungsnummer als Parameter entgegennimmt.
Firebird-Version
SQL-Quelltext:
-
CREATE OR ALTER PROCEDURE Mitarbeiter_aus_Abteilung
-
( Abt INTEGER) -
RETURNS ( Personalnummer VARCHAR(10),
-
Name VARCHAR(30), -
Vorname VARCHAR(30), -
Geburtsdatum DATE ) -
AS -
BEGIN
-
FOR SELECT Personalnummer, Name, Vorname, Geburtsdatum
-
FROM Mitarbeiter -
WHERE Abteilung_ID = :Abt -
ORDER BY Ist_Leiter, Name, Vorname -
INTO :Personalnummer, :Name, :Vorname, :Geburtsdatum -
DO SUSPEND;
-
END
Als Prozedur enthält die Abfrage folgende Bestandteile:
- Als Eingabe-Parameter wird die gewünschte Abteilungsnummer erwartet und in der Variablen Abt gespeichert.
- Als Ausgabe-Parameter werden die gewünschten Spalten der Tabelle Mitarbeiter aufgeführt.
- Weitere Variable werden nicht benötigt.
Zwischen BEGIN und END ab Zeile 8 steht der eigentliche Arbeitsablauf mit Anweisungen gemäß Programmieren mit SQL.
- Es handelt sich dabei vor allem um einen SELECT-Befehl mit der gewünschten WHERE-Klausel, die bei der View nicht möglich war.
- Diese Abfrage wird in eine FOR-DO-Schleife eingebunden.
- Dabei werden die Spalten eines jeden ausgewählten Datensatzes mit INTO in die Ausgabe-Variablen übertragen und per SUSPEND zurückgegeben.
Damit haben wir die gewünschte variable Abfrage. Bei der Verwendung ist es für den Anwender gleichgültig, ob er sie als View oder als Prozedur aufzurufen hat:
Hole alle Mitarbeiter einer bestimmten Abteilung.
[Bearbeiten] Testdaten in einer Tabelle erzeugen
Das übliche Vorgehen, wenn in einer Tabelle viele Testdaten gespeichert werden sollen, geht so:
- In Zusatztabellen werden geeignete Feldinhalte gesammelt: eine Tabelle mit möglichen Vornamen, eine mit Nachnamen usw.
- Wie unter Einfache Tabellenverknüpfung beschrieben werden alle Zeilen und Spalten aller Zusatztabellen miteinander kombiniert.
- Oder die Zeilen und Spalten der Zusatztabellen werden per Zufallsfunktion miteinander verknüpft.
Bei wenigen Spalten der Zieltabelle kann auf Zusatztabellen verzichtet werden; dieses Verfahren nutzen wir in diesem Beispiel. (Das Verfahren ist theoretisch auch für andere Tabellen möglich, wird dann aber schnell unübersichtlich.)
Erzeuge eine beliebige Anzahl von Datensätzen in der Tabelle Fahrzeug; die Anzahl der neuen Einträge soll als Parameter angegeben werden.
Firebird-Version
SQL-Quelltext:
-
CREATE OR ALTER PROCEDURE Insert_Into_Fahrzeug
-
( Anzahl INTEGER = 0) -
RETURNS ( Maxid INTEGER)
-
AS -
DECLARE VARIABLE Temp INTEGER = 0;
-
DECLARE VARIABLE Listekz CHAR(120) = 'K K K E E E MH MH DU DU WESBOTRE RE OB OB GE GE HERHAMBO BO DO DO UN UN D D D ME ME EN EN W W W HA HA KR NE ';
-
DECLARE VARIABLE Listekza CHAR( 26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
-
DECLARE VARIABLE Listefrb CHAR(100) = 'elfenbein schwarz gelb orange ocker blau silbern grau braun weiss ';
-
DECLARE VARIABLE Kz VARCHAR( 3);
-
DECLARE VARIABLE Name VARCHAR(30);
-
DECLARE VARIABLE Rand1 INTEGER = 0;
-
DECLARE VARIABLE Rand2 INTEGER = 0;
-
DECLARE VARIABLE Rand3 INTEGER = 0;
-
DECLARE VARIABLE Rand4 INTEGER = 0;
-
DECLARE VARIABLE Rand5 INTEGER = 0;
-
BEGIN
-
Maxid = 0;
-
WHILE (Temp < Anzahl) DO
-
BEGIN
-
Rand1 = FLOOR(1 + (RAND() * 39)); /* eine Zufallszahl zwischen 1 und 40 (je einschl.) */ -
Rand2 = FLOOR(0 + (RAND() * 26)); /* eine Zufallszahl zwischen 0 und 26 */ -
Rand3 = FLOOR(1 + (RAND() * 25)); /* eine Zufallszahl zwischen 1 und 26 */ -
Rand4 = FLOOR(1 + (RAND() * 9)); /* eine Zufallszahl zwischen 1 und 10 */ -
Rand5 = FLOOR(1 + (RAND() * 22)); /* eine Zufallszahl zwischen 1 und 23 */ -
Kz = TRIM(SUBSTRING(:Listekz FROM (:Rand1*3 - 2) FOR 3)); -
Name = Kz || '-' || CASE :Rand2 -
WHEN 0 THEN '' -
ELSE SUBSTRING(:Listekza FROM :Rand2 FOR 1) -
END -
|| SUBSTRING(:Listekza FROM :Rand3 FOR 1) -
|| ' ' || CAST( (CASE CHAR_LENGTH(Kz) -
WHEN 1 THEN FLOOR(1 + (RAND() * 9998)) -
ELSE FLOOR(1 + (RAND() * 998)) -
END) -
AS INT); -
INSERT INTO Fahrzeug (Kennzeichen, Farbe, Fahrzeugtyp_ID) -
VALUES ( :Name, -
TRIM(SUBSTRING(:Listefrb FROM (:Rand4*10-9) FOR 10)), -
:Rand5 ); -
Temp = Temp + 1; -
END
-
SELECT MAX(ID) FROM fahrzeug INTO :Maxid;
-
SUSPEND;
-
END
Dies definiert die Prozedur mit folgenden Bestandteilen:
- Mit Anzahl als einzigem Eingabe-Parameter wird festgelegt, wie viele Datensätze erzeugt werden sollen. Dazu gehören der Name und Typ der Variablen sowie ein Vorgabewert.
- Mit Maxid als einzigem Ausgabe-Parameter wird angegeben, dass ein INTEGER-Wert mit diesem Namen erwartet wird. Der Anfangswert wird im ersten Befehl des "Körpers" der Prozedur festgelegt.
- Zwischen AS und BEGIN stehen die verwendeten Variablen mit ihren Anfangswerten.
- Listekz ist eine Liste von 40 Kfz-Kennzeichen, Listekza enthält das Alphabet für die Zufallssuche nach dem zweiten Teil des Kennzeichens und Listefrb eine Liste von 10 Farben (zu je 10 Zeichen). Diese Listen ersetzen die temporären Tabellen.
- Temp wird als Zähler für die neuen Datensätze verwendet, Kz und Name als Zwischenspeicherung bei der Bildung des neuen Kennzeichens.
- Rand1 usw. sind Variablen für Zufallszahlen. Darauf könnte auch verzichtet werden, weil die Formeln gemäß Funktionen (2) auch direkt in der String-Verknüpfung und dem VALUES-Teil verwendet werden könnten; aber die Trennung macht es übersichtlicher.
Zwischen BEGIN und END ab Zeile 16 steht der eigentliche Arbeitsablauf mit Anweisungen gemäß Programmieren mit SQL. Innerhalb der WHILE-Schleife zwischen DO BEGIN und END wird jeweils ein neuer Datensatz erzeugt und gespeichert:
- Zeile 20 bis 24 liefern neue Zufallszahlen.
- In Zeile 25 wird durch Rand1 eine der Zahlen 1, 4, 7 usw. berechnet und gemäß diesem Wert eines der Kennzeichen aus Listekz geholt.
- In Zeile 26 ff. wird daraus ein vollständiges Kfz-Kennzeichen:
- Wenn Rand2 ungleich 0 ist, folgt ein Buchstabe nach der Zufallszahl Rand2 aus dem Alphabet, sonst nichts.
- Danach folgt ein weiterer Buchstabe nach der Zufallszahl Rand3 aus dem Alphabet.
- Danach folgt eine (Zufalls-) Zahl: bei einstelligem Kennzeichen eine vierstellige Zahl, sonst eine dreistellige.
- In Zeile 36 ff. wird ein neuer Datensatz eingetragen mit dem eben erzeugten Kennzeichen sowie:
- einer Farbe, die mit der Zufallszahl Rand4 aus Listefrb geholt wird
- einer Fahrzeugtyp-ID, die nach der Zufallszahl Rand5 einem der vorhandenen Werte in der Tabelle Fahrzeugtyp entspricht
Zusätzlich wird die Variable Temp weitergezählt; nach dem Ende der Arbeit wird der größte Wert von ID bestimmt, in die Variable Maxid eingetragen und per SUSPEND als Rückgabewert übernommen.
Ausgeführt wird diese Prozedur durch einen einfachen Befehl.
Erzeuge 10 neue Datensätze in der Tabelle Fahrzeug.
EXECUTE PROCEDURE Insert_into_Fahrzeug (10);
------ Procedure executing results: ------ MAXID = 145
[Bearbeiten] INSERT in mehrere Tabellen
Wenn in der Beispieldatenbank ein neuer Versicherungsvertrag eingegeben werden soll, benötigt man in der Regel drei INSERT-Befehle:
- für einen neuen Eintrag in der Tabelle Fahrzeug
- für einen neuen Eintrag in der Tabelle Versicherungsnehmer
- für einen neuen Eintrag in der Tabelle Versicherungsvertrag mit Verweisen auf die beiden anderen Einträge
Man benötigt also drei INSERT-Befehle; bei den ersten beiden ist durch SELECT die eingetragene ID abzufragen und beim dritten INSERT zu verwenden. Warum sollte man sich die Arbeit in dieser Weise erschweren? Soll doch die Datenbank einen INSERT-Befehl entgegennehmen und die Parameter selbständig auf die beteiligten Tabellen verteilen.
Speichere einen neuen Versicherungsvertrag mit allen Einzelheiten.
Firebird-Version
SQL-Quelltext:
-
CREATE OR ALTER procedure Insert_Versicherungsvertrag
-
( /* Bestandteile des Vertrags */
-
Vertragsnummer VARCHAR(20), -
Abschlussdatum DATE, -
Art CHAR(2), -
Praemiensatz INTEGER, -
Basispraemie DECIMAL(9,0), -
Mitarbeiter_ID INTEGER, -
/* Angaben zum Versicherungsnehmer */ -
Name VARCHAR(30), -
Vorname VARCHAR(30), -
Geschlecht CHAR(1), -
Geburtsdatum DATE, -
Fuehrerschein DATE, -
Ort VARCHAR(30), -
PLZ CHAR(5), -
Strasse VARCHAR(30), -
Hausnummer VARCHAR(10), -
Eigener_Kunde CHAR(1), -
Versicherungsgesellschaft_ID INTEGER, -
/* Angaben zum Fahrzeug */ -
Kennzeichen VARCHAR(10), -
Farbe VARCHAR(30), -
Fahrzeugtyp_ID INTEGER -
) -
RETURNS( NewID INTEGER )
-
AS -
DECLARE VARIABLE NewFahrzeugID integer;
-
DECLARE VARIABLE NewVersnehmerID integer;
-
BEGIN
-
NewFahrzeugID = NEXT VALUE FOR Fahrzeug_ID;
-
INSERT INTO Fahrzeug
-
VALUES ( :NewFahrzeugID, :Kennzeichen, :Farbe, :Fahrzeugtyp_ID ); -
NewVersnehmerID = NEXT VALUE FOR Versicherungsnehmer_ID;
-
INSERT INTO Versicherungsnehmer
-
VALUES ( :NewVersnehmerID, :Name, :Vorname, :Geburtsdatum, :Fuehrerschein, -
:Ort, :PLZ, :Strasse, :Hausnummer, -
:Eigener_Kunde, :Versicherungsgesellschaft_ID, :Geschlecht ); -
NewID = NEXT VALUE FOR Versicherungsvertrag_ID;
-
INSERT INTO Versicherungsvertrag
-
VALUES ( :NewID, :Vertragsnummer, :Abschlussdatum, :Art, :Mitarbeiter_ID, -
:NewFahrzeugID, :NewVersnehmerID, :Praemiensatz, :Abschlussdatum, :Basispraemie ); -
SUSPEND;
-
END
Als Eingabe-Parameter werden alle Werte benötigt, die der Mitarbeiter für die einzelnen Tabellen eingeben muss:
- in den Zeilen 3 bis 8 die Angaben für Versicherungsvertrag
- in den Zeilen 10 bis 20 die Angaben für Versicherungsnehmer
- in den Zeilen 22 bis 24 die Angaben für Fahrzeug
Als Ausgabe-Parameter wollen wir die ID für den neuen Vertrag erhalten.
Als Variable werden die Verweise auf die zugeordneten Tabellen Fahrzeug und Versicherungsnehmer vorgesehen.
Der Arbeitsablauf ist ganz einfach strukturiert:
- Hole die nächste ID für die Tabelle Fahrzeug und speichere den nächsten Eintrag in dieser Tabelle.
- Hole die nächste ID für die Tabelle Versicherungsnehmer und speichere den nächsten Eintrag in dieser Tabelle.
- Hole die nächste ID für die Tabelle Versicherungsvertrag und speichere den nächsten Eintrag in dieser Tabelle. Benutze dafür die IDs der beiden anderen Tabellen.
Damit wird ein neuer Vertrag mit einem einzigen Befehl erstellt:
Speichere einen neuen Vertrag mit allen Angaben.
Firebird-Version
SQL-Quelltext:
EXECUTE PROCEDURE Insert_Versicherungsvertrag ( 'HS-38', '03.11.2009', 'VK', 125, 870, 11, 'Graefing', 'Charlotte', 'W', '09.11.1989', '26.02.2008', 'Hattingen', '45529', 'Laakerweg', '17 b', 'J', NULL, 'BO-MC 413', 'gelb', 8 );
------ Procedure executing results: ------ NEWID = 29 /* die ID des neuen Vertrags */
[Bearbeiten] Automatisches UPDATE gemäß Bedingungen
Bei einer Versicherungsgesellschaft muss regelmäßig der Prämiensatz eines Vertrags neu berechnet werden: Bei verschuldeten Schadensfällen wird er (abhängig von der Höhe des Schadens) erhöht, bei Schadensfreiheit verringert. Dies ist eine Aufgabe, die die Datenbank selbständig erledigen kann und soll. Das ist ein komplexer Arbeitsablauf mit mehreren Prüfungen; vor allem die unterschiedliche Zuordnung neuer Werte wird hier stark vereinfacht.
Berechne für alle (eigenen) Verträge, ob eine neue Prämienrechnung ansteht. Dabei ist zu prüfen, ob wegen neuer Schadensfälle der Prämiensatz zu erhöhen oder wegen Schadensfreiheit zu verringern ist.
Die einzelnen Schritte werden anhand des folgenden Codes erläutert.
Firebird-Version
SQL-Quelltext:
-
CREATE OR ALTER PROCEDURE Update_Praemiensatz
-
( Aktualisierung DATE DEFAULT CURRENT_DATE ) -
RETURNS ( Erledigt INTEGER ) -
AS -
DECLARE VARIABLE current_id INTEGER;
-
DECLARE VARIABLE current_fz INTEGER;
-
DECLARE VARIABLE current_aenderung DATE;
-
DECLARE VARIABLE vergleich_aenderung DATE;
-
DECLARE VARIABLE current_satz INTEGER;
-
DECLARE VARIABLE current_value DECIMAL(16,2);
-
BEGIN
-
Erledigt = 0;
-
SUSPEND;
-
-
/* Vorarbeit: Anfänger werden auf Praemiensatz 200 gesetzt; das kann aber -
nur für noch nicht behandelte Verträge gelten und muss deshalb noch -
vor der nächsten IS NULL-Prüfung erledigt werden. */ -
UPDATE Versicherungsvertrag vv -
SET Praemiensatz = 200 -
WHERE Praemienaenderung IS NULL
-
AND Versicherungsnehmer_ID -
/* bestimme die Liste der Anfänger, aber nur für eigene Kunden */ -
IN ( SELECT ID -
FROM Versicherungsnehmer vn -
/* wenn der Führerschein beim Vertragsabschluss weniger als 2 Jahre alt ist */ -
WHERE vn.Eigener_kunde = 'J' -
AND ( ( DATEADD( YEAR, 2, vn.Fuehrerschein ) > vv.Abschlussdatum ) -
/* wenn der VersNehmer beim Führerschein noch nicht 21 Jahre alt ist */ -
OR ( DATEADD( YEAR, 21, vn.Geburtsdatum ) > vn.Fuehrerschein ) ) ); -
-
/* zu bearbeiten sind alle Verträge für eigene Kunden, deren letzte -
Prämienänderung "zu lange" zurückliegt */ -
FOR SELECT vv.ID, Fahrzeug_ID,
-
CASE -
WHEN Praemienaenderung IS NULL THEN Abschlussdatum -
ELSE Praemienaenderung -
END, Praemiensatz -
FROM Versicherungsvertrag vv -
JOIN Versicherungsnehmer vn ON vn.Id = vv.Versicherungsnehmer_id -
WHERE vn.Eigener_Kunde = 'J' -
AND ( Praemienaenderung IS NULL OR Praemienaenderung <= :Aktualisierung) -
INTO :current_id, :current_fz, :current_aenderung, :current_satz -
DO BEGIN
-
/* wegen der Übersicht das mögliche Schlussdatum vorher bestimmen */ -
vergleich_aenderung = DATEADD( YEAR, 1, current_aenderung ); -
vergleich_aenderung = DATEADD( DAY, -1, vergleich_aenderung ); -
-
/* weitere Bearbeitung, sofern die Aktualisierung über das -
Vergleichsdatum hinausgeht */ -
IF (Aktualisierung >= vergleich_aenderung) THEN -
BEGIN -
-
/* suche zu diesem Vertrag, d.h. diesem Fahrzeug alle Schadensfälle in dieser Zeit -
und summiere die Schadenssumme nach Schuldanteil */ -
SELECT SUM( sf.Schadenshoehe * zu.Schuldanteil / 100 ) -
FROM Zuordnung_SF_FZ zu -
JOIN Schadensfall sf ON zu.Schadensfall_id = sf.Id -
WHERE zu.Fahrzeug_ID = :current_fz -
AND sf.Datum BETWEEN :current_aenderung AND :vergleich_aenderung -
INTO :current_value; -
-
/* abhängig von (anteiliger) Schadenssumme und bisherigem Prämiensatz -
wird der neue Prämiensatz bestimmt und das Datum weitergesetzt */ -
UPDATE Versicherungsvertrag -
SET Praemiensatz = -
CASE -
WHEN :current_value IS NULL THEN CASE -
WHEN :current_satz > 100 -
THEN :current_satz - 20 -
WHEN :current_satz BETWEEN 40 AND 100 -
THEN :current_satz - 10 -
ELSE 30 -
END -
WHEN :current_value = 0 THEN :current_satz - 10 -
WHEN :current_value < 500 THEN :current_satz -
WHEN :current_value < 1000 THEN :current_satz + 10 -
WHEN :current_value >= 1000 THEN :current_satz + 30 -
END, -
Praemienaenderung = DATEADD( YEAR, 1, :current_aenderung ) -
WHERE ID = :current_id; -
Erledigt = Erledigt + 1; -
END -
END
-
-
SUSPEND;
-
END
Als Eingabe-Parameter wird nur das Datum der derzeitigen Aktualisierung benötigt. Ein Vertrag wird dann geprüft, wenn der Zeitraum der nächsten Prämienrechnung – vom Datum der letzten Prämienänderung aus ein Jahr – das Datum der Aktualisierung enthält. Dieses Verfahren stellt sicher, dass immer nur "neue" Schadensfälle und nur die jeweils anstehenden Prämienrechnungen berücksichtigt werden.
Als Ausgabe-Parameter nehmen wir nur die Anzahl der berechneten Verträge, also der geänderten Erledigt-Vermerke.
Als Variable benötigen wir Zwischenwerte für alle Angaben, die im Arbeitsablauf benutzt werden.
Der Arbeitsablauf umfasst die folgenden Punkte:
- In Zeile 13 wird der Rückgabewert mit dem Anfangswert belegt und angezeigt zum Zeichen dafür, dass die Prozedur arbeitet.
- In Zeile 19 bis 30 wird ein "Anfänger", der als Eigener_Kunde gespeichert ist, auf einen Prämiensatz von 200 gesetzt. Als Anfänger gilt, wenn der Führerschein beim Abschluss des Vertrags noch nicht zwei Jahre alt ist oder wenn der Führerschein vor dem 21. Geburtstag erworben wurde.
- Dabei handelt es sich um einen sachlichen Fehler, der erst jetzt beim Schreiben und nicht beim Programmieren aufgefallen ist: Danach wäre auch ein neuer Vertrag nach 20 Jahren Fahrpraxis immer noch ein Anfänger-Vertrag. In der Praxis müssen solche Bedingungen vorher genau formuliert werden.
- In Zeile 34 bis 42 werden die Verträge ausgewählt, die "im Moment" zu prüfen und ggf. zu bearbeiten sind: alle eigenen Kunden, deren Prämiensatz noch nie geprüft wurde (dann wird das Abschlussdatum, also das Datum der ersten Rechnung, zur Berechnung verwendet) oder deren letzte Prämienänderung vor dem Datum der Aktualisierung liegt.
- Durch die FOR-SELECT-Schleife wird jeder dieser Verträge innerhalb von DO BEGIN-END in Zeile 44 ff. einzeln bearbeitet; die dazu benötigten Werte werden in Zeile 43 in den Variablen zwischengespeichert.
- In Zeile 46/47 wird das Schlussdatum der letzten Prämienrechnung berechnet. Wir arbeiten hier nur mit Jahresrechnungen; da bei BETWEEN beide Grenzwerte einbezogen werden, müssen wir das Schlussdatum um einen Tag verringern.
- In Zeile 51 kommt die letzte Vorprüfung:
- Wenn das Datum der Aktualisierung vor dem Schlussdatum liegt, soll noch keine neue Rechnung erfolgen.
- Aber wenn das Datum der Aktualisierung das Schlussdatum der letzten Prämienrechnung überschreitet, ist eine neue Rechnung und damit eine Überprüfung des Prämiensatzes fällig.
- In Zeile 56 bis 61 wird die Summe aller Schadensfälle berechnet:
- Berücksichtigt wird die Summe der Schadenshöhe je Vorfall, also aus der Tabelle Schadensfall.
- Dieser Wert wird bei der Summierung anteilig nach dem Schuldanteil aus der Tabelle Zuordnung_SF_FZ berücksichtigt.
- Das Ergebnis bei current_value lautet NULL, wenn das Fahrzeug nicht in einen Schadensfall verwickelt wurde, es lautet 0 bei fehlender Teilschuld und größer als 0 bei Mit- oder Vollschuld.
- In Zeile 65 ff. wird der Prämiensatz neu berechnet:
- Ohne Schadensfall wird er um 10 Punkte verringert, wenn er bisher kleiner/gleich 100 beträgt, und um 20 Punkte, wenn er bisher größer ist.
- Das Minimum von 30 kann nicht unterschritten werden.
- Mit Schadensfall ohne Teilschuld wird der Prämiensatz um 10 Punkte verringert.
- Bei einem sehr geringen Schaden bleibt der Prämiensatz unverändert.
- Bei einem kleineren Schaden wird er um 10 Punkte, sonst um 30 Punkte erhöht.
- Gleichzeitig wird das Datum der Prämienänderung um 1 Jahr weitergesetzt.
Mit dieser Prozedur können die neuen Prämiensätze berechnet und für die nächste Prämienrechnung vorbereitet werden:
Berechne die Prämiensätze für alle (eigenen) Verträge, bei denen im 3. Quartal 2009 eine neue Prämienrechnung ansteht.
EXECUTE PROCEDURE Update_Praemiensatz('30.09.2009');
------ Procedure executing results: ------ Erledigt = 7
[Bearbeiten] Zusammenfassung
In diesem Kapitel lernten wir Prozeduren für Arbeitsabläufe kennen, die "nur" innerhalb der Datenbank ausgeführt werden:
- Eine Prozedur kann mit oder ohne Argumenten und mit oder ohne Rückgabewerte ausgeführt werden.
- Sie dient zur automatischen Erledigung von Aufgaben, die "von außen" angestoßen werden, aber keine zusätzlichen Maßnahmen des Anwenders oder der Anwendung benötigen.
- Bei einem solchen Arbeitsablauf werden viele Bestandteile einer Programmiersprache benutzt.