Oracle PL/SQL – Packages

Fach: Informationstechnologie – Datenbanksysteme
Schulstufe: 12. Schulstufe – HTL Informatik
Voraussetzungen: Prozeduren und Funktionen, Cursor, Exceptions
Autor: HTL Pinkafeld – IF/IT



1. Was ist ein Package?

1.1 Definition

Ein Package (Paket) ist eine benannte PL/SQL-Einheit, die logisch zusammengehörige Typen, Variablen, Konstanten, Cursor, Prozeduren und Funktionen bündelt. Es besteht aus zwei Teilen:

Package Specification (Spec)    Package Body
─────────────────────────────   ─────────────────────────────
Öffentliche Schnittstelle        Implementierung
  • Typen                          • Prozedur-/Funktions-Code
  • Variablen/Konstanten           • Private Elemente
  • Cursor-Deklarationen           • Initialisierungsabschnitt
  • Prozedur-/Funktions-Köpfe

1.2 Vorteile von Packages

Vorteil Beschreibung
Kapselung Implementierungsdetails verstecken
Modularität Logisch zusammengehöriger Code in einer Einheit
Performance Gesamtes Package wird beim ersten Aufruf in Speicher geladen
Sitzungszustand Package-Variablen bleiben für die ganze Session bestehen
Overloading Gleicher Name, unterschiedliche Parameter
Abhängigkeitsmanagement Body kann neu kompiliert werden, ohne Spec zu ändern

2. Package Specification

2.1 Syntax

CREATE [OR REPLACE] PACKAGE paketname IS
    -- Öffentliche Typen, Konstanten, Variablen
    -- Cursor-Deklarationen (nur Header)
    -- Prozedur-/Funktions-Header (ohne Body)
END [paketname];
/

2.2 Beispiel: pkg_mitarbeiter

CREATE OR REPLACE PACKAGE pkg_mitarbeiter IS

    -- Konstanten
    c_min_gehalt CONSTANT NUMBER := 1700;
    c_max_gehalt CONSTANT NUMBER := 50000;

    -- Öffentlicher Typ
    TYPE t_emp_info IS RECORD (
        empno  emp.empno%TYPE,
        ename  emp.ename%TYPE,
        sal    emp.sal%TYPE,
        abt    dept.dname%TYPE
    );

    -- Prozedur-Köpfe (Schnittstelle)
    PROCEDURE gehaltserhöhung (p_empno emp.empno%TYPE, p_prozent NUMBER);
    PROCEDURE zeige_abteilung (p_deptno dept.deptno%TYPE);

    -- Funktions-Köpfe
    FUNCTION jahresgehalt (p_empno emp.empno%TYPE) RETURN NUMBER;
    FUNCTION ist_manager  (p_empno emp.empno%TYPE) RETURN BOOLEAN;

    -- Öffentlicher Cursor-Typ (REF CURSOR)
    TYPE t_emp_cursor IS REF CURSOR RETURN emp%ROWTYPE;

END pkg_mitarbeiter;
/

3. Package Body

3.1 Syntax

CREATE [OR REPLACE] PACKAGE BODY paketname IS
    -- Private Variablen, Typen (nicht in Spec sichtbar)
    -- Implementierung aller in der Spec deklarierten Subprogramme
    -- Zusätzliche private Subprogramme
    [BEGIN
        -- Initialisierungsabschnitt (einmalig bei erstem Aufruf)
    ]
END [paketname];
/

3.2 Implementierung von pkg_mitarbeiter

CREATE OR REPLACE PACKAGE BODY pkg_mitarbeiter IS

    -- Private Variable (nur im Body sichtbar)
    v_aufrufe NUMBER := 0;

    -- ─── PROZEDUREN ───────────────────────────────────────────────────────────
    PROCEDURE gehaltserhöhung (p_empno emp.empno%TYPE, p_prozent NUMBER) IS
        v_alt emp.sal%TYPE;
        v_neu emp.sal%TYPE;
    BEGIN
        IF p_prozent <= 0 OR p_prozent > 50 THEN
            RAISE_APPLICATION_ERROR(-20010, 'Ungültiger Prozentsatz: ' || p_prozent);
        END IF;

        SELECT sal INTO v_alt FROM emp WHERE empno = p_empno;
        v_neu := ROUND(v_alt * (1 + p_prozent / 100), 2);

        IF v_neu > c_max_gehalt THEN
            RAISE_APPLICATION_ERROR(-20011, 'Gehalt würde Maximum überschreiten.');
        END IF;

        UPDATE emp SET sal = v_neu WHERE empno = p_empno;
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('Gehalt: ' || v_alt || ' → ' || v_neu);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE_APPLICATION_ERROR(-20012, 'Mitarbeiter ' || p_empno || ' nicht gefunden.');
    END gehaltserhöhung;

    PROCEDURE zeige_abteilung (p_deptno dept.deptno%TYPE) IS
    BEGIN
        v_aufrufe := v_aufrufe + 1;  -- Private Variable zählen
        DBMS_OUTPUT.PUT_LINE('--- Abteilung ' || p_deptno || ' ---');
        FOR r IN (
            SELECT e.ename, e.job, e.sal
            FROM   emp e
            WHERE  e.deptno = p_deptno
            ORDER  BY e.sal DESC
        ) LOOP
            DBMS_OUTPUT.PUT_LINE(
                RPAD(r.ename, 12) || RPAD(r.job, 10) || r.sal
            );
        END LOOP;
    END zeige_abteilung;

    -- ─── FUNKTIONEN ───────────────────────────────────────────────────────────
    FUNCTION jahresgehalt (p_empno emp.empno%TYPE) RETURN NUMBER IS
        v_sal  emp.sal%TYPE;
        v_comm emp.comm%TYPE;
    BEGIN
        SELECT sal, NVL(comm, 0)
        INTO   v_sal, v_comm
        FROM   emp
        WHERE  empno = p_empno;

        RETURN (v_sal + v_comm) * 12;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN RETURN NULL;
    END jahresgehalt;

    FUNCTION ist_manager (p_empno emp.empno%TYPE) RETURN BOOLEAN IS
        v_cnt NUMBER;
    BEGIN
        SELECT COUNT(*) INTO v_cnt
        FROM   emp
        WHERE  mgr = p_empno;
        RETURN v_cnt > 0;
    END ist_manager;

END pkg_mitarbeiter;
/

3.3 Aufrufen des Packages

BEGIN
    -- Package-Elemente über Punkt-Notation aufrufen:
    pkg_mitarbeiter.gehaltserhöhung(7369, 5);
    pkg_mitarbeiter.zeige_abteilung(20);

    IF pkg_mitarbeiter.ist_manager(7839) THEN
        DBMS_OUTPUT.PUT_LINE('King ist Manager.');
    END IF;

    DBMS_OUTPUT.PUT_LINE(
        'Jahresgehalt King: ' || pkg_mitarbeiter.jahresgehalt(7839)
    );
END;
/

4. Package-Variablen und Sitzungszustand

4.1 Lebensdauer

Package-Variablen leben für die gesamte Datenbankverbindung (Session). Jede Session hat ihre eigene Kopie:

CREATE OR REPLACE PACKAGE pkg_zaehler IS
    -- Öffentliche Variable – bleibt die ganze Session erhalten
    v_gesamt_aufrufe  NUMBER := 0;

    PROCEDURE zähle_aufruf (p_aktion VARCHAR2);
    FUNCTION  hole_zähler   RETURN NUMBER;
END pkg_zaehler;
/

CREATE OR REPLACE PACKAGE BODY pkg_zaehler IS
    PROCEDURE zähle_aufruf (p_aktion VARCHAR2) IS
    BEGIN
        v_gesamt_aufrufe := v_gesamt_aufrufe + 1;
        DBMS_OUTPUT.PUT_LINE('[' || v_gesamt_aufrufe || '] ' || p_aktion);
    END;

    FUNCTION hole_zähler RETURN NUMBER IS
    BEGIN
        RETURN v_gesamt_aufrufe;
    END;
END pkg_zaehler;
/

-- Test:
BEGIN
    pkg_zaehler.zähle_aufruf('Login');
    pkg_zaehler.zähle_aufruf('Abfrage');
    pkg_zaehler.zähle_aufruf('Update');
    DBMS_OUTPUT.PUT_LINE('Gesamt: ' || pkg_zaehler.hole_zähler());
END;
/
-- Ausgabe: [1] Login, [2] Abfrage, [3] Update, Gesamt: 3

4.2 SERIALLY_REUSABLE

Mit dem Pragma SERIALLY_REUSABLE werden Package-Variablen nach jedem Aufruf zurückgesetzt – nützlich, wenn kein Sitzungszustand erwünscht ist:

CREATE OR REPLACE PACKAGE pkg_einmalig IS
    PRAGMA SERIALLY_REUSABLE;
    v_wert NUMBER := 0;
    PROCEDURE setze (p_wert NUMBER);
END;
/

5. Overloading im Package

5.1 Gleicher Name, verschiedene Signaturen

CREATE OR REPLACE PACKAGE pkg_format IS
    -- Drei Versionen von "formatiere":
    FUNCTION formatiere (p_zahl NUMBER,     p_maske VARCHAR2 DEFAULT 'FM999,990.00') RETURN VARCHAR2;
    FUNCTION formatiere (p_datum DATE,      p_maske VARCHAR2 DEFAULT 'DD.MM.YYYY')   RETURN VARCHAR2;
    FUNCTION formatiere (p_text  VARCHAR2)  RETURN VARCHAR2;
END pkg_format;
/

CREATE OR REPLACE PACKAGE BODY pkg_format IS
    FUNCTION formatiere (p_zahl NUMBER, p_maske VARCHAR2 DEFAULT 'FM999,990.00') RETURN VARCHAR2 IS
    BEGIN
        RETURN TO_CHAR(p_zahl, p_maske);
    END;

    FUNCTION formatiere (p_datum DATE, p_maske VARCHAR2 DEFAULT 'DD.MM.YYYY') RETURN VARCHAR2 IS
    BEGIN
        RETURN TO_CHAR(p_datum, p_maske);
    END;

    FUNCTION formatiere (p_text VARCHAR2) RETURN VARCHAR2 IS
    BEGIN
        RETURN '"' || TRIM(p_text) || '"';
    END;
END pkg_format;
/

BEGIN
    DBMS_OUTPUT.PUT_LINE(pkg_format.formatiere(1234567.89));
    DBMS_OUTPUT.PUT_LINE(pkg_format.formatiere(SYSDATE));
    DBMS_OUTPUT.PUT_LINE(pkg_format.formatiere('  Hallo Welt  '));
END;
/

6. Private und öffentliche Elemente

6.1 Sichtbarkeit

Element Definiert in Sichtbar für
Öffentlich Package Spec Alle (außerhalb des Packages aufrufbar)
Privat Package Body Nur innerhalb des Package Body
CREATE OR REPLACE PACKAGE pkg_demo IS
    -- ÖFFENTLICH:
    FUNCTION öffentliche_funktion (p_x NUMBER) RETURN NUMBER;
END pkg_demo;
/

CREATE OR REPLACE PACKAGE BODY pkg_demo IS
    -- PRIVAT (nur intern verwendbar):
    FUNCTION private_hilfsfunktion (p_x NUMBER) RETURN NUMBER IS
    BEGIN
        RETURN p_x * p_x;
    END;

    -- Öffentliche Funktion nutzt private:
    FUNCTION öffentliche_funktion (p_x NUMBER) RETURN NUMBER IS
    BEGIN
        RETURN private_hilfsfunktion(p_x) + 1;
    END;
END pkg_demo;
/

7. Initialisierungsabschnitt

7.1 Einmalige Initialisierung

Der BEGIN-Abschnitt am Ende des Package Body wird genau einmal pro Session ausgeführt – beim ersten Aufruf eines Package-Elements:

CREATE OR REPLACE PACKAGE pkg_config IS
    v_server_name  VARCHAR2(50);
    v_db_version   VARCHAR2(20);
    v_start_zeit   DATE;
END pkg_config;
/

CREATE OR REPLACE PACKAGE BODY pkg_config IS
BEGIN
    -- Einmalige Initialisierung:
    SELECT host_name   INTO v_server_name FROM v$instance;
    SELECT version     INTO v_db_version  FROM v$instance;
    v_start_zeit := SYSDATE;

    DBMS_OUTPUT.PUT_LINE('Package initialisiert: ' || TO_CHAR(v_start_zeit, 'HH24:MI:SS'));
EXCEPTION
    WHEN OTHERS THEN
        v_server_name := 'Unbekannt';
        v_db_version  := 'Unbekannt';
END pkg_config;
/

8. Wichtige Oracle Standard-Packages

8.1 DBMS_OUTPUT

BEGIN
    DBMS_OUTPUT.ENABLE(1000000);      -- Puffer aktivieren (1 MB)
    DBMS_OUTPUT.PUT_LINE('Zeile 1');
    DBMS_OUTPUT.PUT('Teiltext ');
    DBMS_OUTPUT.NEW_LINE;
END;
/

8.2 DBMS_UTILITY

DECLARE
    v_start  NUMBER;
    v_ende   NUMBER;
BEGIN
    v_start := DBMS_UTILITY.GET_TIME();  -- Centisekunden

    -- Langsame Operation simulieren:
    FOR i IN 1..100000 LOOP NULL; END LOOP;

    v_ende := DBMS_UTILITY.GET_TIME();
    DBMS_OUTPUT.PUT_LINE('Dauer: ' || (v_ende - v_start) || ' cs');
END;
/

8.3 UTL_FILE – Dateizugriff

DECLARE
    v_file UTL_FILE.FILE_TYPE;
BEGIN
    -- Datei schreiben (Verzeichnis muss in DBA_DIRECTORIES definiert sein):
    v_file := UTL_FILE.FOPEN('MY_DIR', 'ausgabe.txt', 'W');
    UTL_FILE.PUT_LINE(v_file, 'Erste Zeile');
    UTL_FILE.PUT_LINE(v_file, 'Zweite Zeile: ' || TO_CHAR(SYSDATE));
    UTL_FILE.FCLOSE(v_file);
    DBMS_OUTPUT.PUT_LINE('Datei geschrieben.');
END;
/

8.4 DBMS_SCHEDULER – Jobs planen

BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'JOB_DAILY_CLEANUP',
        job_type        => 'PLSQL_BLOCK',
        job_action      => 'BEGIN cleanup_alte_logs(30); END;',
        start_date      => SYSTIMESTAMP,
        repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=0',
        enabled         => TRUE,
        comments        => 'Tägliche Bereinigung nach 30 Tagen'
    );
END;
/

8.5 Übersicht häufig verwendeter Packages

Package Verwendung
DBMS_OUTPUT Textausgabe (Debugging)
DBMS_UTILITY Hilfsfunktionen (Zeit, DDL-Analyse)
DBMS_SQL Dynamisches SQL
DBMS_LOB LOB-Verarbeitung
DBMS_SCHEDULER Job-Planung
DBMS_STATS Statistiken für Optimizer
UTL_FILE Dateizugriff (Server-Seite)
UTL_MAIL E-Mail senden
UTL_HTTP HTTP-Anfragen
DBMS_CRYPTO Kryptographie
DBMS_RANDOM Zufallszahlen

9. Packages verwalten

9.1 Kompilieren und Status prüfen

-- Package Spec und Body kompilieren:
ALTER PACKAGE pkg_mitarbeiter COMPILE;
ALTER PACKAGE pkg_mitarbeiter COMPILE BODY;

-- Status aller Packages prüfen:
SELECT object_name, object_type, status, last_ddl_time
FROM   user_objects
WHERE  object_type IN ('PACKAGE', 'PACKAGE BODY')
ORDER  BY object_name, object_type;

-- Fehler nach Kompilierung:
SELECT line, position, text
FROM   user_errors
WHERE  name = 'PKG_MITARBEITER'
ORDER  BY type, sequence;

9.2 Quellcode anzeigen

-- Spec anzeigen:
SELECT text FROM user_source
WHERE  name = 'PKG_MITARBEITER' AND type = 'PACKAGE'
ORDER  BY line;

-- Body anzeigen:
SELECT text FROM user_source
WHERE  name = 'PKG_MITARBEITER' AND type = 'PACKAGE BODY'
ORDER  BY line;

9.3 Package löschen

-- Nur Body löschen (Spec bleibt):
DROP PACKAGE BODY pkg_mitarbeiter;

-- Spec und Body löschen:
DROP PACKAGE pkg_mitarbeiter;

10. Zusammenfassung und Ausblick

10.1 Package-Aufbau auf einen Blick

pkg_mitarbeiter (Package)
├── Specification (öffentliche Schnittstelle)
│   ├── Konstanten: c_min_gehalt, c_max_gehalt
│   ├── Typen: t_emp_info, t_emp_cursor
│   ├── Prozeduren: gehaltserhöhung, zeige_abteilung
│   └── Funktionen: jahresgehalt, ist_manager
└── Body (Implementierung)
    ├── Private Variable: v_aufrufe
    ├── Implementierung aller Spec-Subprogramme
    └── Private Hilfsprozeduren/-funktionen

10.2 Wann ein Package?

Situation Empfehlung
Einzelne unabhängige Prozedur/Funktion Standalone Subprogramm
Mehrere zusammengehörige Subprogramme Package
Sitzungsweite Variablen nötig Package
Overloading gewünscht Package
API für andere Benutzer/Schemas Package mit klarer Spec

10.3 Ausblick

Das letzte Kapitel behandelt Trigger – PL/SQL-Code, der automatisch beim Auftreten bestimmter Datenbankereignisse ausgeführt wird:

Vorheriges ThemaProzeduren & Funktionen Nächstes ThemaTrigger