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:

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:

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 %ROWTYPE automatisch 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 immer IS NULL / IS NOT NULL verwenden!


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 INTO muss 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:

Start der ReiheKein vorheriges Thema Nächstes ThemaKontrollstrukturen