Inhaltsverzeichnis
Oracle PL/SQL – Einführung und Grundstruktur
Fach: Informationstechnologie – Datenbanksysteme
Schulstufe: 12. Schulstufe – HTL Informatik
Voraussetzungen: Oracle SQL (DDL, DML, DQL, Joins, Subqueries)
Autor: HTL Pinkafeld – IF/IT
1. Was ist PL/SQL?
1.1 Definition und Einordnung
PL/SQL (Procedural Language/Structured Query Language) ist Oracles prozedurale Erweiterung von SQL. Während reines SQL eine deskriptive Sprache ist (man beschreibt was man will), erlaubt PL/SQL zusätzlich:
- Variablen, Datentypen und Zuweisungen
- Kontrollstrukturen (IF, LOOP, FOR, WHILE)
- Fehlerbehandlung (Exception Handling)
- Wiederverwendbare Programmmodule (Prozeduren, Funktionen, Pakete)
- Automatische Reaktion auf Datenbankoperationen (Trigger)
SQL → Deklarativ: „Gib mir alle Mitarbeiter mit Gehalt > 3000"
PL/SQL → Prozedural: „Für jeden Mitarbeiter: prüfe Gehalt, gib Bonus, speichere Ergebnis"
1.2 Geschichte und Relevanz
| Version | Jahr | Neuerungen |
|---|---|---|
| PL/SQL 1.0 | 1991 | Grundstruktur, Basis-Datentypen |
| PL/SQL 2.x | 1992–1997 | Packages, Stored Procedures |
| PL/SQL 8i | 1998 | OOP-Erweiterungen, Large Objects |
| PL/SQL 9i | 2001 | Native Compilation |
| PL/SQL 11g | 2007 | Compound Triggers, CONTINUE |
| PL/SQL 12c+ | 2013+ | WITH-Klausel für Funktionen, Inline-Pragmas |
| PL/SQL 21c+ | 2021+ | JavaScript-Integration, JSON-Typen |
1.3 Ausführungsumgebungen
PL/SQL-Code läuft direkt auf dem Datenbankserver – nicht auf dem Client. Das hat entscheidende Vorteile:
- Weniger Netzwerkverkehr: Mehrere SQL-Statements werden in einem Block gebündelt
- Sicherheit: Logik liegt in der Datenbank, nicht im Client-Code
- Performance: Zugriff auf Daten ohne Netzwerk-Round-Trip
Client (SQL Developer / Anwendung)
│ 1× PL/SQL-Block senden
▼
Oracle Database Server
├── PL/SQL Engine (Prozeduraler Code)
└── SQL Engine (SQL-Statements innerhalb des Blocks)
│ Ergebnis zurück
▼
Client
2. Der anonyme PL/SQL-Block
2.1 Grundstruktur
Ein PL/SQL-Block besteht aus bis zu vier Abschnitten:
DECLARE
-- Deklarationsabschnitt (optional)
-- Variablen, Konstanten, Typen, Cursor
BEGIN
-- Ausführungsabschnitt (Pflicht)
-- SQL-Statements und PL/SQL-Anweisungen
EXCEPTION
-- Fehlerbehandlungsabschnitt (optional)
-- Reagiert auf Laufzeitfehler
END;
/
Hinweis: Das
/am Ende sendet den Block in SQL*Plus oder SQL Developer zur Ausführung.
2.2 Minimaler Block
Der einfachste gültige PL/SQL-Block besteht nur aus BEGIN und END:
BEGIN
NULL; -- Leerer Block; NULL ist die "Keine-Operation"-Anweisung
END;
/
2.3 Erstes Beispiel
DECLARE
v_name VARCHAR2(50) := 'HTL Pinkafeld';
v_jahr NUMBER := 2025;
BEGIN
DBMS_OUTPUT.PUT_LINE('Willkommen bei ' || v_name || ' – Jahrgang ' || v_jahr);
END;
/
Ausgabe:
Willkommen bei HTL Pinkafeld – Jahrgang 2025
2.4 Verschachtelte Blöcke
PL/SQL-Blöcke können ineinander verschachtelt werden. Innere Blöcke haben Zugriff auf die Variablen des äußeren Blocks, aber nicht umgekehrt:
DECLARE
v_aussen VARCHAR2(20) := 'Außen';
BEGIN
DECLARE
v_innen VARCHAR2(20) := 'Innen';
BEGIN
DBMS_OUTPUT.PUT_LINE(v_aussen || ' und ' || v_innen); -- OK
END;
-- DBMS_OUTPUT.PUT_LINE(v_innen); -- FEHLER: v_innen nicht sichtbar
END;
/
3. Variablen und Datentypen
3.1 Deklarationssyntax
variablenname datentyp [NOT NULL] [:= startwert | DEFAULT startwert];
Beispiele:
DECLARE
v_vorname VARCHAR2(30); -- NULL (kein Startwert)
v_nachname VARCHAR2(50) NOT NULL := 'Muster'; -- Pflicht-Startwert
v_gehalt NUMBER(8,2) DEFAULT 0; -- Startwert 0
v_aktiv BOOLEAN := TRUE; -- Boolean
v_datum DATE := SYSDATE; -- Aktuelles Datum
BEGIN
v_vorname := 'Max';
DBMS_OUTPUT.PUT_LINE(v_vorname || ' ' || v_nachname);
END;
/
3.2 Skalare Datentypen – Übersicht
| Kategorie | Typ | Beschreibung | Beispiel |
|---|---|---|---|
| Zeichenketten | VARCHAR2(n) |
Var. Länge, max. 32767 Byte | 'Hallo' |
| Zeichenketten | CHAR(n) |
Feste Länge, aufgefüllt | 'A ' |
| Zahlen | NUMBER(p,s) |
Festkomma, p Stellen, s Nachkomma | NUMBER(8,2) |
| Zahlen | INTEGER |
Ganzzahl (≙ NUMBER(38)) | 42 |
| Zahlen | PLS_INTEGER |
Schnelle Integer-Arithmetik | 1000 |
| Datum/Zeit | DATE |
Datum + Uhrzeit (Sekunden) | SYSDATE |
| Datum/Zeit | TIMESTAMP |
Datum + Uhrzeit (Nanosekunden) | SYSTIMESTAMP |
| Logisch | BOOLEAN |
TRUE / FALSE / NULL | TRUE |
| Groß-Daten | CLOB |
Character Large Object | Texte >32 KB |
| Groß-Daten | BLOB |
Binary Large Object | Bilder, PDFs |
3.3 Zeichenketten-Operationen
DECLARE
v_vor VARCHAR2(20) := 'Max';
v_nach VARCHAR2(20) := 'Mustermann';
v_voll VARCHAR2(41);
BEGIN
v_voll := v_vor || ' ' || v_nach; -- Konkatenation mit ||
DBMS_OUTPUT.PUT_LINE(UPPER(v_voll)); -- MAX MUSTERMANN
DBMS_OUTPUT.PUT_LINE(LENGTH(v_voll)); -- 13
DBMS_OUTPUT.PUT_LINE(SUBSTR(v_voll, 1, 3)); -- Max
DBMS_OUTPUT.PUT_LINE(INSTR(v_voll, 'Mann')); -- Position von 'Mann'
END;
/
4. Konstanten und Skalare Typen
4.1 Konstanten
Mit dem Schlüsselwort CONSTANT wird eine Variable unveränderlich. Ein Startwert ist Pflicht:
DECLARE
c_mwst CONSTANT NUMBER := 0.20; -- 20 % MwSt.
c_pi CONSTANT NUMBER := 3.14159265;
c_firma CONSTANT VARCHAR2(50) := 'HTL Pinkafeld';
BEGIN
DBMS_OUTPUT.PUT_LINE('MwSt: ' || (100 * c_mwst) || ' %');
-- c_mwst := 0.19; -- FEHLER: Konstanten können nicht geändert werden
END;
/
4.2 Subtypes
Mit SUBTYPE können eigene benannte Typen basierend auf bestehenden Typen definiert werden:
DECLARE
SUBTYPE t_name IS VARCHAR2(50);
SUBTYPE t_gehalt IS NUMBER(8,2);
v_mitarbeiter t_name := 'Anna Bauer';
v_lohn t_gehalt := 3450.00;
BEGIN
DBMS_OUTPUT.PUT_LINE(v_mitarbeiter || ': ' || v_lohn || ' €');
END;
/
5. %TYPE und %ROWTYPE
5.1 %TYPE – Typübernahme einer Spalte
Mit %TYPE übernimmt eine Variable automatisch den Datentyp einer Tabellenspalte. Das macht den Code robuster gegenüber Schema-Änderungen:
DECLARE
-- v_ename hat denselben Typ wie die Spalte ENAME in EMP
v_ename emp.ename%TYPE;
v_sal emp.sal%TYPE;
v_deptno emp.deptno%TYPE := 10;
BEGIN
SELECT ename, sal
INTO v_ename, v_sal
FROM emp
WHERE empno = 7369;
DBMS_OUTPUT.PUT_LINE(v_ename || ' verdient ' || v_sal || ' USD');
END;
/
5.2 %ROWTYPE – Typübernahme einer gesamten Zeile
Mit %ROWTYPE kann eine Variable eine ganze Tabellenzeile aufnehmen. Die Felder entsprechen den Spalten der Tabelle:
DECLARE
v_emp emp%ROWTYPE; -- Enthält alle Felder der Tabelle EMP
BEGIN
SELECT *
INTO v_emp
FROM emp
WHERE empno = 7839;
DBMS_OUTPUT.PUT_LINE('Name: ' || v_emp.ename);
DBMS_OUTPUT.PUT_LINE('Job: ' || v_emp.job);
DBMS_OUTPUT.PUT_LINE('Abteilung: ' || v_emp.deptno);
DBMS_OUTPUT.PUT_LINE('Gehalt: ' || v_emp.sal);
END;
/
Vorteil: Wenn Spalten zur Tabelle hinzugefügt oder Typen geändert werden, passt sich
%ROWTYPEautomatisch an – kein Code muss geändert werden.
6. Zusammengesetzte Typen – RECORD
6.1 RECORD-Typ definieren
Ein RECORD ist ein benutzerdefinierter zusammengesetzter Typ, ähnlich einem Struct in C oder einer Klasse in Java (ohne Methoden):
DECLARE
-- Eigenen RECORD-Typ definieren
TYPE t_person IS RECORD (
vorname VARCHAR2(30),
nachname VARCHAR2(50),
gebdatum DATE,
aktiv BOOLEAN := TRUE
);
-- Variable dieses Typs anlegen
v_person t_person;
BEGIN
v_person.vorname := 'Maria';
v_person.nachname := 'Huber';
v_person.gebdatum := TO_DATE('2005-09-15', 'YYYY-MM-DD');
DBMS_OUTPUT.PUT_LINE(v_person.vorname || ' ' || v_person.nachname);
DBMS_OUTPUT.PUT_LINE('Geboren: ' || TO_CHAR(v_person.gebdatum, 'DD.MM.YYYY'));
END;
/
6.2 RECORD vs. %ROWTYPE
| Merkmal | RECORD | %ROWTYPE |
|---|---|---|
| Felder | Frei definierbar | Entsprechen Tabellenspalten |
| Flexibilität | Hoch | An Tabelle gebunden |
| Typsicherheit | Manuell sicherstellen | Automatisch durch Schema |
| Typisch für | Zwischenergebnisse, Parameter | Tabellenzeilen lesen/schreiben |
7. DBMS_OUTPUT – Ausgabe in PL/SQL
7.1 Aktivierung
Damit Ausgaben sichtbar sind, muss die Ausgabe aktiviert werden:
-- In SQL*Plus oder SQL Developer:
SET SERVEROUTPUT ON
-- Oder im PL/SQL-Block selbst:
DBMS_OUTPUT.ENABLE(1000000); -- Puffer 1 MB
7.2 Ausgabe-Prozeduren
BEGIN
-- Zeile mit Zeilenumbruch ausgeben:
DBMS_OUTPUT.PUT_LINE('Zeile 1');
DBMS_OUTPUT.PUT_LINE('Zeile 2');
-- Ohne Zeilenumbruch:
DBMS_OUTPUT.PUT('Teil A ');
DBMS_OUTPUT.PUT('Teil B');
DBMS_OUTPUT.NEW_LINE; -- Expliziter Umbruch
-- Zahlenwerte müssen in VARCHAR2 umgewandelt werden:
DBMS_OUTPUT.PUT_LINE('Wert: ' || TO_CHAR(42.5, '999.99'));
END;
/
7.3 Zahlenformate mit TO_CHAR
DECLARE
v_zahl NUMBER := 1234567.89;
BEGIN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_zahl, '9,999,999.99')); -- 1,234,567.89
DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_zahl, 'FM999G999D99')); -- 1234567.89 (kein Leerzeichen)
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SYSDATE, 'DD.MM.YYYY HH24:MI'));
END;
/
8. Skalarausdrücke und Zuweisungen
8.1 Zuweisungsoperator
PL/SQL verwendet := für Zuweisungen (nicht = wie in anderen Sprachen). Der =-Operator dient ausschließlich dem Vergleich:
DECLARE
v_a NUMBER := 10;
v_b NUMBER := 3;
v_ergebnis NUMBER;
BEGIN
v_ergebnis := v_a + v_b; -- Addition
v_ergebnis := v_a - v_b; -- Subtraktion
v_ergebnis := v_a * v_b; -- Multiplikation
v_ergebnis := v_a / v_b; -- Division (Achtung: Gleitkomma!)
v_ergebnis := v_a ** 2; -- Potenz (10²)
v_ergebnis := MOD(v_a, v_b); -- Modulo (Rest der Division)
DBMS_OUTPUT.PUT_LINE('10 mod 3 = ' || v_ergebnis); -- 1
END;
/
8.2 Vergleichsoperatoren
| Operator | Bedeutung | Beispiel |
|---|---|---|
= |
Gleich | v_x = 5 |
<> oder != |
Ungleich | v_x <> 0 |
<, > |
Kleiner/Größer | v_x < 100 |
<=, >= |
Kleiner-Gleich/Größer-Gleich | v_x >= 18 |
IS NULL |
Ist NULL | v_name IS NULL |
IS NOT NULL |
Ist nicht NULL | v_name IS NOT NULL |
LIKE |
Muster-Vergleich | v_name LIKE 'M%' |
BETWEEN |
Bereichsvergleich | v_alter BETWEEN 18 AND 65 |
IN |
In Wertemenge | v_dept IN (10, 20, 30) |
8.3 Logische Operatoren
DECLARE
v_x NUMBER := 15;
v_aktiv BOOLEAN := TRUE;
BEGIN
IF v_x > 10 AND v_aktiv THEN
DBMS_OUTPUT.PUT_LINE('Bedingung erfüllt');
END IF;
IF v_x < 5 OR NOT v_aktiv THEN
DBMS_OUTPUT.PUT_LINE('Alternative');
END IF;
END;
/
NULL-Logik:
NULL AND TRUE = NULL,NULL OR TRUE = TRUE,NOT NULL = NULL
Bei Vergleichen mit NULL immerIS NULL/IS NOT NULLverwenden!
9. SQL in PL/SQL – SELECT INTO
9.1 Einzelwert abfragen
Das SELECT INTO-Statement liest genau eine Zeile in Variablen:
DECLARE
v_ename VARCHAR2(10);
v_sal NUMBER;
BEGIN
SELECT ename, sal
INTO v_ename, v_sal
FROM emp
WHERE empno = 7839;
DBMS_OUTPUT.PUT_LINE('Chef: ' || v_ename || ', Gehalt: ' || v_sal);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Kein Mitarbeiter gefunden!');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Mehr als eine Zeile gefunden!');
END;
/
Wichtig:
SELECT INTOmuss genau eine Zeile zurückliefern.
Kein Ergebnis →NO_DATA_FOUND, mehr als eine Zeile →TOO_MANY_ROWS
9.2 Aggregatfunktionen
Aggregatfunktionen liefern immer genau einen Wert – sicher für SELECT INTO:
DECLARE
v_anzahl NUMBER;
v_max_sal NUMBER;
v_avg_sal NUMBER;
BEGIN
SELECT COUNT(*), MAX(sal), AVG(sal)
INTO v_anzahl, v_max_sal, v_avg_sal
FROM emp
WHERE deptno = 20;
DBMS_OUTPUT.PUT_LINE('Mitarbeiter: ' || v_anzahl);
DBMS_OUTPUT.PUT_LINE('Höchstgehalt: ' || v_max_sal);
DBMS_OUTPUT.PUT_LINE('Durchschnitt: ' || ROUND(v_avg_sal, 2));
END;
/
9.3 DML in PL/SQL
INSERT, UPDATE, DELETE und MERGE können direkt im PL/SQL-Block verwendet werden:
DECLARE
v_empno NUMBER := 9999;
BEGIN
-- INSERT
INSERT INTO emp (empno, ename, job, deptno, sal, hiredate)
VALUES (v_empno, 'TESTUSER', 'CLERK', 10, 2000, SYSDATE);
-- UPDATE
UPDATE emp
SET sal = sal * 1.10
WHERE empno = v_empno;
-- DELETE
DELETE FROM emp WHERE empno = v_empno;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Transaktionen abgeschlossen.');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Fehler: ' || SQLERRM);
END;
/
10. Zusammenfassung und Ausblick
10.1 Kernkonzepte dieses Kapitels
| Konzept | Beschreibung |
|---|---|
| Anonymer Block | DECLARE – BEGIN – EXCEPTION – END |
| Variablen | variablenname datentyp [:= wert] |
%TYPE |
Übernimmt Datentyp einer Spalte |
%ROWTYPE |
Übernimmt alle Spalten einer Tabelle |
RECORD |
Benutzerdefinierter zusammengesetzter Typ |
DBMS_OUTPUT |
Textausgabe zu Debugging-Zwecken |
SELECT INTO |
Einzelzeile in Variablen einlesen |
| DML | INSERT/UPDATE/DELETE direkt im Block |
10.2 Typische Fehler für Anfänger
| Fehler | Ursache | Lösung |
|---|---|---|
PLS-00201: Identifier must be declared |
Variable nicht deklariert | Im DECLARE-Abschnitt deklarieren |
ORA-01403: no data found |
SELECT INTO findet keine Zeile | EXCEPTION WHEN NO_DATA_FOUND |
ORA-01422: exact fetch returns more rows |
SELECT INTO findet mehrere Zeilen | WHERE-Klausel präzisieren oder Cursor verwenden |
:= vergessen |
Zuweisung mit = statt := |
Immer := für Zuweisungen verwenden |
10.3 Ausblick
Im nächsten Kapitel werden Kontrollstrukturen behandelt:
IF / ELSIF / ELSEfür VerzweigungenCASEfür mehrfache AuswahlLOOP,WHILE,FORfür SchleifenEXITundCONTINUEzur Schleifensteuerung