PLSQL-Cursor: Das Cursorkonzept

Cursor werden benutzt, um Daten aus ORACLE-Tabellen in ein PL/SQL-Programm zu übernehmen und damit sehr luxuriös zu arbeiten.
Für jede Ausführung eines SQL-Befehls wird Memory allokiert. PL/SQL erlaubt es nun, dieses Memory zu benennen. Diese wird auch Context-Area oder Cursor genannt.

Eine Cursor-Variable ist ähnlich wie ein Zeiger, der in die Context Area zeigt.
Ein PL/SQL-Programm kann nun über den Cursor diesen Speicherbereich kontrollieren.
ACHTUNG: Der Begriff Cursor stellt den Memory-Bereich dar, eine Cursor Variable ist der Pointer auf diesen Bereich.

Wenn eine Cursor-Variable deklariert wird, erhält man einen Pointer, der erstmal nirgendwo hinzeigt. Erst, wenn der Cursor geöffnet wird, wird Memory allokiert und die Cursor-Variable zeigt auf den Cursor. Wenn der Cursor geschlossen wird, wird Speicher freigegeben, die Cursor-Variable ist dann nicht mehr nutzbar.

Explizite Cursor

ORACLE unterscheidet zwischen impliziten und expliziten Cursor. Die impliziten werden quasi durch Ausführung von SQL-Kommandos automatisch erzeugt und erlauben nur eine geringe Kontrolle des Speicherbereichs.

Deklaration eines Cursor

Explizite Cursor werden benutzt, wenn eine Query mehr als einen Datensatz zurückliefert und werden im DECLARE-Teil bekannt gemacht.

DECLARE  
  CURSOR emp_cur IS SELECT ename FROM EMP;  
BEGIN  
  ----  
  ---  
END;

Die Verarbeitung von mehreren Ergebnis-Zeilen ist ganz ähnlich zur Arbeit mit Dateien. Um Dateiinhalte zu verarbeiten, muss das File geöffnet werden, Datensatz für Datensatz verarbeitet werden und im Anschluss die Datei wieder geschlossen werden.
So wie ein File-Pointer die aktuelle Position in einer Datei markiert, markiert eine Cursor-Variable die aktuelle Position innerhalb der ermittelten Datensätze.

Öffnen eines Cursor

DECLARE  
  CURSOR emp_cur IS SELECT ename FROM EMP;  
BEGIN      
  OPEN emp_cur; 
END;

Wenn ein Cursor geöffnet wird, wird das dahinterliegende SELECT-Kommando ausgeführt und die zu liefernden Datensätze ermittelt.
Die Cursor-Variable zeigt im Anschluss auf den ersten ermittelten Datensatz.

Fetchen aus dem Cursor

Um eine Zeile aus einem Cursor zu holen, müssen wir die Zeile aus dem Cursor in eine entsprechende PL/SQL-Variable übernehmen. Der Pointer zeigt im Anschluss automatisch auf den nächsten Datensatz, so daß ein weiteres Fetch die zweite Zeile liefert usw. Sind keine weiteren Datensätze im Cursor enthalten, liefert ein Fetch nicht etwa eine Exception, sondern NULL.

DECLARE     
  CURSOR emp_cur IS SELECT ename FROM EMP;     
  my_name emp.ename%TYPE; 
BEGIN      
  OPEN emp_cur;     
  FETCH emp_cur INTO my_name;     
  dbms_output.put_line(my_name);     
  FETCH emp_cur INTO my_name;     
  dbms_output.put_line(my_name); 
END;

Schliessen des Cursors

Nachdem die gewünschten Datensätze verarbeitet sind, muß der Cursor wieder geschlossen werden.

DECLARE     
  CURSOR emp_cur IS SELECT ename FROM EMP;     
  my_name emp.ename%TYPE; 
BEGIN      
  OPEN emp_cur;     
  FETCH emp_cur INTO my_name;     
  dbms_output.put_line(my_name);     
  FETCH emp_cur INTO my_name;     
  dbms_output.put_line(my_name);     
  CLOSE emp_cur; 
END;

Auch wenn bei anonymen Blöcken Cursor automatisch geschlossen werden, sollte man dennoch stets explizit schliessen. Das ist zum einen gute Programmierpraxis, zum anderen gibt es auch Situationen, in denen ein Cursor versehentlich geöffnet bleibt und damit unnötig Speicher verbraucht (z.B. beim Einsatz in Packages).

Ein Cursor liefert also sequentiell die mit dem SELECT ermittelten Datensätze. Ein Fetch liefert stets den nächsten anstehenden Satz, ein Hin- und Herspringen (ala “liefer mir den vorletzten erneut”) geht nicht (vergleichbar mit einer einfach verketteten Liste). Nachdem der Cursor geschlossen ist, ist ein Fetch nicht mehr möglich. Wenn der Cursor erneut geöffnet wird, wird auch das Select wieder ausgeführt und das Spiel kann von neuem beginnen.

Cursor Attribute

Jeder explizite Cursor hat vier Attribute, die an den Cursornamen “angehängt” werden können, um weitere Informationen zu erhalten.

  • %NOTFOUND: Dieses Attribut liefert TRUE, wenn bei einem Cursor-Fetch keine weiteren Datensätze gefunden werden
  • %FOUND: Dieses Attribut liefert TRUE, wenn bei einem letzten Cursor-Fetch noch ein Datensatz gefunden wurde (also das Gegenteil von %NOTFOUND)
  • %ROWCOUNT: ein numerischer Wert, der die Anzahl bereits gefetchter Datensätze enthält
  • %ISOPEN: ist TRUE, wenn der Cursor offen ist, andernfalls FALSE.

Folgendes Beispiel gibt alle Mitarbeiternamen am Bildschirm aus.

set serveroutput on 
DECLARE     
  CURSOR emp_cur IS SELECT ename FROM EMP;     
  my_name emp.ename%TYPE; 
BEGIN      
  OPEN emp_cur;     
  LOOP         
    FETCH emp_cur INTO my_name;         
    EXIT WHEN emp_cur%NOTFOUND;         
    dbms_output.put_line(my_name);     
  END LOOP;     
  CLOSE emp_cur; 
END;

Die FORCURSORLOOP

Sie wurde schonmal kurz bei den Schleifenkonstrukten erläutert, macht hier aber erst Sinn:

Diese Form der Schleife terminiert automatisch, wenn keine weiteren Datensätze mehr gefetcht werden. Der Cursor wird implizit geöffnet und geschlossen. Auch eine passende Record-Variable wird implizit deklariert. Damit das wird vorhergehendes Beispiel deutlich schlanker.

set serveroutput on 
DECLARE     
  CURSOR emp_cur IS SELECT ename FROM EMP;  
BEGIN     
  FOR myrec IN emp_cur LOOP         
    dbms_output.put_line(myrec.ename);    
  END LOOP; 
END;

Daten ändern mit einem Cursor

Datenveränderungen sind auch mit Hilfe von Cursors möglich. Die Cursor-Deklaration selbst enthält allerdings weiterhin das SELECT-Kommando, nur der Zusatz FOR UPDATE ist neu, und sorgt für ein Sperren der Datensätze bereits beim Lesen.
Wollen Sie nun die verarbeiteten Datensätze verändern, nutzt man die Klausel WHERE CURRENT OF.

DECLARE     
  CURSOR emp_cur IS SELECT ename FROM EMP FOR UPDATE;  
BEGIN     
  FOR myrec IN emp_cur LOOP         
    dbms_output.put_line(myrec.ename);         
    UPDATE emp set sal = sal + 100 WHERE CURRENT OF emp_cur;    
  END LOOP; 
END;

Eine explizite Bedingung mit Primärschlüssel ist also nicht notwendig.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.