Cursor
Ein Cursor ist ein Datenbankobjekt, das als Zeiger auf eine bestimmte Position innerhalb einer Ergebnismenge (Resultset) einer SQL-Abfrage fungiert. Waehrend SQL grundsaetzlich mengenorientiert arbeitet und komplette Datensaetze auf einmal verarbeitet, ermoeglichen Cursor die zeilenweise Verarbeitung einzelner Datensaetze - aehnlich wie ein Dateizeiger in der klassischen Programmierung.
Das Konzept ist besonders dann nuetzlich, wenn du fuer jeden Datensatz eine individuelle, komplexe Verarbeitung durchfuehren musst, die sich nicht einfach in einer einzigen SQL-Anweisung ausdruecken laesst. Als Fachinformatiker fuer Anwendungsentwicklung wirst du Cursor vor allem in Stored Procedures und bei der Implementierung komplexer Geschaeftslogik einsetzen.
Cursor in Oracle PL/SQL
Oracle bietet mit PL/SQL eine elegante Cursor-Syntax, besonders die Cursor FOR Loop, die vieles automatisch erledigt:
Typische Anwendungsfaelle fuer Cursor sind:
- Komplexe Geschaeftslogik: Wenn fuer jeden Datensatz individuelle Berechnungen oder Validierungen noetig sind
- Zeilenweise Datenverarbeitung: Bei der schrittweisen Verarbeitung grosser Datenmengen
- Interaktive Anwendungen: Wenn Benutzer durch Datensaetze navigieren (z.B. Produktlisten)
- Datenmigrationen: Bei der Transformation von Daten zwischen verschiedenen Systemen
- Reporting: Fuer komplexe Berichte, die Zeile fuer Zeile aufgebaut werden
Der Lebenszyklus eines Cursors
Ein Cursor durchlaeuft einen klar definierten Lebenszyklus mit fuenf Phasen. Das Verstaendnis dieser Phasen ist grundlegend fuer die korrekte Arbeit mit Cursorn.
1. DECLARE - Cursor deklarieren
In der ersten Phase wird der Cursor mit einem Namen versehen und die zugehoerige SELECT-Abfrage definiert. Hier legst du auch fest, welchen Typ der Cursor haben soll und ob er nur lesend oder auch schreibend auf die Daten zugreifen darf.
2. OPEN - Cursor oeffnen
Beim Oeffnen wird die SELECT-Abfrage ausgefuehrt und die Ergebnismenge erstellt. Der Cursor-Zeiger wird vor den ersten Datensatz positioniert. Je nach Cursor-Typ wird das Resultset komplett in den Speicher geladen oder bei Bedarf abgerufen.
3. FETCH - Daten abrufen
Mit FETCH holst du den naechsten (oder bei scrollbaren Cursorn einen beliebigen) Datensatz aus der Ergebnismenge. Die Werte werden in Variablen uebertragen, die du dann in deiner Programmlogik verwenden kannst. Der Cursor bewegt sich automatisch zur naechsten Position.
4. CLOSE - Cursor schliessen
Nach der Verarbeitung wird der Cursor geschlossen. Dabei wird die Ergebnismenge freigegeben, aber die Cursor-Definition bleibt erhalten. Du kannst den Cursor spaeter erneut oeffnen, falls noetig.
5. DEALLOCATE - Ressourcen freigeben
Im letzten Schritt wird die Cursor-Definition vollstaendig entfernt und alle zugehoerigen Ressourcen werden freigegeben. Dies ist wichtig fuer die Speicherverwaltung und sollte nie vergessen werden.
Cursor-Typen
Datenbanksysteme bieten verschiedene Cursor-Typen mit unterschiedlichen Eigenschaften. Die Wahl des richtigen Typs beeinflusst sowohl die Funktionalitaet als auch die Performance deiner Anwendung.
Forward-Only Cursor
Der einfachste und effizienteste Cursor-Typ. Er kann nur vorwaerts durch die Daten navigieren - vom ersten zum letzten Datensatz. Da kein Scrolling unterstuetzt wird, verbraucht er minimal Ressourcen. Dieser Typ wird auch Firehose Cursor genannt und ist die beste Wahl fuer einfache, sequenzielle Verarbeitung.
Static Cursor
Erstellt beim Oeffnen eine vollstaendige Momentaufnahme der Daten. Aenderungen an den zugrundeliegenden Tabellen werden nicht sichtbar - der Cursor zeigt immer den Zustand zum Zeitpunkt des Oeffnens. Das garantiert Konsistenz, benoetigt aber mehr Speicher und kann bei grossen Datenmengen langsam sein.
Dynamic Cursor
Das Gegenteil des Static Cursors: Er zeigt immer die aktuellsten Daten an. Alle INSERT-, UPDATE- und DELETE-Operationen anderer Benutzer werden sofort sichtbar. Dies ist ideal fuer Echtzeitanwendungen, kostet aber mehr Performance wegen der staendigen Synchronisation mit der Datenbank.
Keyset-Driven Cursor
Ein Kompromiss zwischen Static und Dynamic: Beim Oeffnen wird ein Keyset (Sammlung von Primaerschluesseln) erstellt. Die eigentlichen Datenwerte werden erst beim FETCH aus der Datenbank geholt. Updates an bestehenden Zeilen sind sichtbar, neue Zeilen (INSERTs) hingegen nicht.
Praktisches Beispiel in T-SQL
Das folgende Beispiel zeigt einen typischen Cursor in Microsoft SQL Server, der alle Datenbanken sichert:
-- Variablen deklarieren
DECLARE @dbName VARCHAR(100)
DECLARE @backupPath VARCHAR(500)
-- Cursor deklarieren
DECLARE db_cursor CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN ('tempdb')
-- Cursor oeffnen
OPEN db_cursor
-- Ersten Datensatz holen
FETCH NEXT FROM db_cursor INTO @dbName
-- Schleife durch alle Datensaetze
WHILE @@FETCH_STATUS = 0
BEGIN
-- Backup-Pfad erstellen
SET @backupPath = 'C:\Backup\' + @dbName + '.bak'
-- Backup ausfuehren
BACKUP DATABASE @dbName TO DISK = @backupPath
-- Naechsten Datensatz holen
FETCH NEXT FROM db_cursor INTO @dbName
END
-- Cursor schliessen und freigeben
CLOSE db_cursor
DEALLOCATE db_cursor
Die Systemvariable @@FETCH_STATUS zeigt den Status der letzten FETCH-Operation an: 0 bedeutet Erfolg, -1 zeigt einen Fehler an, und -2 bedeutet, dass die Zeile nicht mehr existiert.
DECLARE
-- Cursor deklarieren
CURSOR emp_cursor IS
SELECT employee_id, first_name, salary
FROM employees
WHERE department_id = 10;
BEGIN
-- Cursor FOR Loop: oeffnet, fetcht und schliesst automatisch
FOR emp_rec IN emp_cursor LOOP
-- Fuer jeden Mitarbeiter: Gehalt um 10% erhoehen
UPDATE employees
SET salary = salary * 1.10
WHERE employee_id = emp_rec.employee_id;
DBMS_OUTPUT.PUT_LINE('Gehaltsanpassung fuer: ' || emp_rec.first_name);
END LOOP;
COMMIT;
END;
Die Cursor FOR Loop ist in Oracle die bevorzugte Methode, da sie den Code vereinfacht und haeufige Fehler wie vergessene CLOSE-Anweisungen vermeidet.
Performance: Cursor vs. mengenbasierte Operationen
Wichtig: Cursor sind in der Regel deutlich langsamer als mengenbasierte SQL-Operationen. Der Grund liegt im wiederholten Kontextwechsel zwischen SQL- und Prozedur-Engine bei jedem FETCH. Ein Vergleich verdeutlicht dies:
| Anzahl Zeilen | Set-basiert | Cursor |
|---|---|---|
| 100 | 5 ms | 30 ms |
| 10.000 | 10 ms | 770 ms |
| 100.000 | 20 ms | 7.600 ms |
| 1.000.000 | 140 ms | 75 Sekunden |
Die Faustregel lautet: Vermeide Cursor, wenn eine mengenbasierte Loesung moeglich ist. Ein UPDATE mit WHERE-Klausel ist fast immer schneller als ein Cursor, der jeden Datensatz einzeln aktualisiert.
Optimierungstechnik: BULK COLLECT
Wenn du Cursor verwenden musst, kann die BULK COLLECT-Technik (in Oracle und aehnliche Konstrukte in anderen Systemen) die Performance deutlich verbessern. Statt einzelner FETCHs werden mehrere Zeilen auf einmal geholt:
DECLARE
TYPE emp_table IS TABLE OF employees%ROWTYPE;
l_employees emp_table;
CURSOR emp_cursor IS SELECT * FROM employees;
BEGIN
OPEN emp_cursor;
LOOP
-- 100 Zeilen auf einmal holen
FETCH emp_cursor BULK COLLECT INTO l_employees LIMIT 100;
EXIT WHEN emp_cursor%NOTFOUND;
-- Verarbeitung der 100 Zeilen
FOR i IN 1..l_employees.COUNT LOOP
-- Geschaeftslogik hier
NULL;
END LOOP;
END LOOP;
CLOSE emp_cursor;
END;
BULK COLLECT kann die Performance um den Faktor 3-5 verbessern, indem es die Anzahl der Kontextwechsel reduziert.
Wann solltest du Cursor verwenden?
Trotz der Performance-Nachteile gibt es legitime Anwendungsfaelle fuer Cursor:
- Komplexe zeilenweise Logik: Wenn jede Zeile unterschiedlich behandelt werden muss und dies nicht in SQL ausdrueckbar ist
- Externe Aufrufe: Wenn fuer jeden Datensatz ein externes System aufgerufen werden muss (z.B. API-Calls)
- Schrittweise Commits: Bei sehr grossen Datenmengen, um Transaktions-Logs nicht zu ueberlasten
- Debugging und Protokollierung: Wenn der Verarbeitungsfortschritt nachverfolgt werden soll
- Administrative Aufgaben: Wie im Backup-Beispiel, wo fuer jede Datenbank ein separater Befehl noetig ist
Cursor in verschiedenen Datenbanksystemen
Die grundlegende Funktionsweise von Cursorn ist aehnlich, aber jedes Datenbanksystem hat seine Besonderheiten:
| Datenbanksystem | Besonderheiten |
|---|---|
| SQL Server | Volle Unterstuetzung aller Cursor-Typen, @@FETCH_STATUS fuer Statusabfrage |
| Oracle | Cursor FOR Loop, BULK COLLECT, implizite Cursor fuer SELECT INTO |
| PostgreSQL | Cursor via refcursor-Variablen in PL/pgSQL |
| MySQL | Nur in Stored Procedures, nur Forward-Only, Read-Only |
MySQL hat die staerksten Einschraenkungen: Cursor funktionieren nur in Stored Procedures, sind nicht scrollbar und erlauben keine Datenmodifikation. Fuer komplexere Anforderungen musst du hier oft auf Anwendungslogik ausweichen.
Cursor in der IHK-Pruefung
Fuer die Abschlusspruefung zum Fachinformatiker solltest du folgende Punkte beherrschen:
- Die fuenf Phasen des Cursor-Lebenszyklus (DECLARE, OPEN, FETCH, CLOSE, DEALLOCATE)
- Die Unterschiede zwischen Forward-Only, Static, Dynamic und Keyset-Driven Cursorn
- Wann Cursor sinnvoll sind und wann mengenbasierte Operationen bevorzugt werden sollten
- Grundlegende Syntax in mindestens einem SQL-Dialekt (meist T-SQL oder PL/SQL)
- Das Konzept des Impedance Mismatch zwischen SQL und prozeduralen Sprachen
Quellen und weiterfuehrende Links
- Microsoft Learn: Cursor in SQL Server - Offizielle Microsoft-Dokumentation
- Oracle: Working with Cursors - Best Practices fuer Oracle PL/SQL
- MySQL: Cursors - MySQL-Dokumentation
- PostgreSQL: PL/pgSQL Cursors - PostgreSQL-Handbuch
- Wikipedia: Cursor (databases) - Allgemeine Uebersicht