Die FOR-Schleife in Oracle 21c

01.
Juni
2022
Veröffentlicht von: Dr. Hildegard Asenbauer

Die FOR-Schleife in Oracle war bisher im Vergleich zu anderen Sprachen recht begrenzt in ihren Möglichkeiten. Mit Version 21c hat sich das drastisch geändert. Die neue Version bringt eine Menge an Erweiterungen. In diesem Tipp soll die neue Vielfalt anhand einfacher Beispiele erläutert werden.

 

Erweiterungen zu Stepped Control

Als Stepped Control wird die altbekannte Angabe von Untergrenze und Obergrenze zur Steuerung der Schleifendurchläufe bezeichntet.

Schrittweite
Die erste – überfällige - Erweiterung betrifft die Schrittweite. Bisher konnte nur um 1 hochgezählt bzw. heruntergezählt werden. Brauchte man andere Iterationen, so behalf man sich bisher gerne mit Modulo-Division:

BEGIN
   FOR i IN 2..10 LOOP
      IF MOD (i, 2) = 0
      THEN
         DBMS_OUTPUT.put_line (i);
      END IF;
   END LOOP;
END;
/


Mit Version 21c geht das deutlich schlanker. Dafür wurde die neue BY-Klausel eingeführt:

BEGIN
   FOR i IN 2 .. 10 BY 2 LOOP
      DBMS_OUTPUT.put_line (i);
   END LOOP;
END;
/


Für beide Schleifen ist die Ausgabe identisch:

2
4
6
8
10


Schleifenzähler und Schrittweite müssen noch nicht einmal mehr ganzzahlig sein. Wenn beim Schleifenzähler der Datentyp mit angegeben wird, sind auch Nachkommastellen möglich:

BEGIN
   FOR i NUMBER (3,1) IN 2.7..10 BY 2 LOOP
      DBMS_OUTPUT.put_line (i);
   END LOOP;
END;
/

2,7
4,7
6,7
8,7

BEGIN
   FOR i NUMBER IN 2.7..4 BY 0.5 LOOP
      DBMS_OUTPUT.put_line (i);
   END LOOP;
END;
/

2,7
3,2
3,7


Nur bei expliziter Angabe von NUMBER werden Bruchzahlen ausgegeben, und auch nur dann ist eine nicht-ganzzahlige Schrittweite möglich.
Ohne diese Angabe bleibt das Verhalten sinnvollerweise das gleiche wie bisher auch: Es wird gerundet. Das verhindert, dass ein Upgrade plötzlich unerwartete Seiteneffekte hat.
 

Kombinationen
Jetzt können, durch Kommata getrennt, auch mehrere Iterationsangaben beliebig kombiniert werden:

BEGIN
   FOR i IN 1..3, 9..11, REVERSE 14..17 LOOP
      DBMS_OUTPUT.put_line (i);
   END LOOP;
END;
/
1
2
3
9
10
11
17
16
15
14


Nur der Vollständigkeit halber sei erwähnt, dass es durch Angabe des Schlüsselworts MUTABLE auch möglich wird, innerhalb der Schleife schreibend auf den Schleifenzähler zuzugreifen, was bisher ein absolutes Tabu war; davon wird aber dringend abgeraten!
 

Bedingungen

Zusätzlich können beliebige Bedingungen angegeben werden, um Schleifendurchläufe weiter einzugrenzen, entweder mit WHILE (Stopping Predicate) oder mit WHEN (Skipping Predicate):

BEGIN
   FOR i IN 1..10 WHEN MOD(i,3) = 0, i+1..15 WHILE i < 13 LOOP
      DBMS_OUTPUT.put_line (i);
   END LOOP;
END;
/
3
6
9
11
12


Wie man an diesem Beispiel sieht, kann man bei der Angabe weiterer Iterationen auch Bezug nehmen auf den Wert des Schleifenzählers nach Beendigung der vorhergegangenen Iterations-Angabe.
Man sieht auch, dass i zwar sehr wohl den Wert 10 annahm, für diesen Wert aber der Schleifendurchlauf übersprungen wurde – daher Skipping.
Solche Bedingungen sind nicht nur bei Stepped control erlaubt, sondern auch bei den neuen Formen der Iterationskontrolle, die in weiteren beschrieben werden.
 

Weitere Neuerungen bei Iterationsangaben

Single Expression Iteration Control
Eine komplett neue Form der Iteration ist die Single Expression Iteration Control, die für sich allein genommen erst einmal sinnfrei erscheint, aber im Zusammenhang mit REPEAT ganz neue Möglichkeiten erschließt:

BEGIN
   FOR i IN 2, REPEAT POWER(i,2) WHILE i < 300 LOOP
      DBMS_OUTPUT.put_line(i);
   END LOOP;
END;
/
2
4
16
256


Die Schleife kann also nicht mehr nur in gleichbleibenden Schritten durchlaufen werden wie bisher; stattdessen kann in der REPEAT-Klausel ein Ausdruck angegeben werden, der von Durchlauf zu Durchlauf die Schrittgöße verändert.
Syntaktisch erlaubt ist es, REPEAT wegzulassen – aber wenig sinnvoll. Für einen einzelnen Wert braucht man schließlich keine Schleife.  
REPEAT ist auch ohne die WHILE-Bedingung erlaubt, aber dann sollte man tunlichst innerhalb der Schleife für eine Abbruchbedingung sorgen!
 

FOR-Schleifen und Collections

Klassischerweise durchläuft man Arrays in einer FOR-Schleife über die Array-Methoden COUNT oder, alternativ, FIRST / LAST, wobei man bei Lücken im Array entsprechende Zusatzbedingungen einbauen muss, wie z. B. die Abfrage auf EXISTS:

DECLARE
   TYPE T IS TABLE OF VARCHAR2(20);
   V_ARR T;
BEGIN
   V_ARR := T('EINS','ZWEI','DREI','VIER');

   FOR I IN 1..V_ARR.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE(V_ARR(I));
   END LOOP;
END;
/
EINS
ZWEI
DREI
VIER


Jetzt gibt es drei neue Formen der Iterationskontrolle, die den Umgang mit Arrays innerhalb der Schleife deutlich vereinfachen können.
 

VALUES
Mit VALUES werden die Werte eines Arrays ausgegeben, egal, ob Zahl oder String, und es führt noch nicht einmal zu einem Fehler, wenn das Array Lücken hat.
Das Array selber muss in der Schleife gar nicht mehr angesprochen werden, der jeweilige Wert steht im Iterator.

DECLARE
   TYPE T IS TABLE OF VARCHAR2(20) INDEX BY PLS_INTEGER;
   V_ARR T;
BEGIN
   V_ARR := T('EINS','ZWEI','DREI','VIER');

   V_ARR.delete(2);
   
   FOR I IN VALUES OF V_ARR LOOP
      DBMS_OUTPUT.PUT_LINE(I);
   END LOOP;
END;
/
EINS
DREI
VIER


Diese Syntax ist nicht nur für Collections zulässig, sondern in ähnlicher Form auch für Cursor und Selects. Darauf soll an dieser Stelle aber nicht weiter eingegangen werden. Beispiele dazu finden sich in der Doku.
 

INDICES
Das Gegenstück zu VALUES ist INDICES. Statt der Werte werden die vorhandenen Indices des Arrays durchlaufen:

DECLARE
   TYPE T IS TABLE OF VARCHAR2(20) INDEX BY PLS_INTEGER;
   V_ARR T;
BEGIN
   V_ARR := T('EINS','ZWEI','DREI','VIER');

   V_ARR.delete(2);
   
   FOR I IN INDICES  OF V_ARR LOOP
      DBMS_OUTPUT.PUT_LINE(I);
   END LOOP;
END;
/
1
3
4

 

PAIRS
Beides kombinieren lässt sich mit PAIRS:

DECLARE
   TYPE T IS TABLE OF VARCHAR2(20) INDEX BY PLS_INTEGER;
   V_ARR T;
BEGIN
   V_ARR := T('EINS','ZWEI','DREI','VIER');

   V_ARR.delete(2);
   
   FOR i,j IN PAIRS OF  V_ARR LOOP
     DBMS_OUTPUT.PUT_LINE(i || '=>'|| j);
   END LOOP;
END;
/
1=>EINS
3=>DREI
4=>VIER


Allen drei neuen Collections-Control-Typen gemein ist, dass sie sogar mit nicht initialisierten Arrays klarkommen. Der folgende Code führt nicht zu einem Fehler:

DECLARE
   TYPE T IS TABLE OF VARCHAR2(20);
   V_ARR T;
BEGIN  
   FOR I IN VALUES OF V_ARR LOOP
      DBMS_OUTPUT.PUT_LINE(I);
   END LOOP;
END;
/


Zum Vergleich:

DECLARE
   TYPE T IS TABLE OF VARCHAR2(20);
   V_ARR T;
BEGIN

   FOR I IN 1..V_ARR.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE(V_ARR(I));
   END LOOP;
END;
/

ORA-06531: Nicht initialisierte Zusammenstellung referenziert

 

Fazit

Die bisher so übersichtliche und über viele Versionen hinweg unveränderte FOR-Schleife ist im Syntax-Graph kaum wiederzuerkennen. Was davon in der Praxis Einzug hält und was eher Spezialfällen vorbehalten ist, wird sich zeigen - und hängt natürlich immer von der jeweiligen Aufgabenstellung ab. Jedenfalls haben sich die Möglichkeiten enorm erweitert.

 

Jede Menge Know-how für Sie!

In unserer Know-How Datenbank finden Sie mehr als 300 ausführliche Beiträge zu den Oracle-Themen wie DBA, SQL, PL/SQL, APEX und vielem mehr.
Hier erhalten Sie Antworten auf Ihre Fragen.