Циклы в PL/SQL
Циклы используются для повторения какого-либо действия несколько раз.
Loop
Простейший цикл в PL/SQL выглядит так:
loop
-- какое-либо действие
end loop;
loop
-- какое-либо действие
end loop;
Данный цикл будет выполнять код между loop
и end loop
бесконечно. Для того, чтобы завершить цикл, можно использовать команду exit
:
declare
i number := 5;
begin
loop
if i = 0 then
exit;
end if;
dbms_output.put_line(i);
i := i - 1;
end loop;
end;
declare
i number := 5;
begin
loop
if i = 0 then
exit;
end if;
dbms_output.put_line(i);
i := i - 1;
end loop;
end;
Результат:
5
4
3
2
1
5
4
3
2
1
Переменная i
отвечает за оставшееся количество повторений. Такие переменные в программировании ещё называют счётчиками цикла.
Для более удобного выхода из цикла можно использовать конструкцию exit when
:
declare
i number := 5;
begin
-- Такой же цикл, как и в предыдущем примере, только
-- читается гораздо лучше
loop
exit when i = 0;
dbms_output.put_line(i);
i := i - 1;
end loop;
end;
declare
i number := 5;
begin
-- Такой же цикл, как и в предыдущем примере, только
-- читается гораздо лучше
loop
exit when i = 0;
dbms_output.put_line(i);
i := i - 1;
end loop;
end;
Exit
можно использовать только внутри цикла, внутри обычного блока его использование приведёт к ошибке:
-- Ошибка!
begin
exit;
end;
-- Ошибка!
begin
exit;
end;
For
Цикл for
удобно использовать для автоматического изменения счётчика цикла:
begin
for i in 1..5
loop
dbms_output.put_line(i);
end loop;
end;
begin
for i in 1..5
loop
dbms_output.put_line(i);
end loop;
end;
Результат:
1
2
3
4
5
1
2
3
4
5
Здесь не использовались ни проверки на необходимость выйти из цикла, ни изменение счётчика цикла - конструкция for
всё сделала за нас. Мы обращаемся к переменной i
- она видима только внутри тела цикла, и недоступна вне его.
В for
указывается нижняя граница счётчика цикла, и верхняя. Изменение производится увеличением счётчика на единицу. Нижняя граница всегда должна быть меньше либо равной верхней границе:
-- Выведет:
-- -5
-- -4
-- -3
-- -2
-- -1
-- 0
-- 1
begin
for i in -5..1
loop
dbms_output.put_line(i);
end loop;
end;
-- Выведет:
-- -5
-- -4
-- -3
-- -2
-- -1
-- 0
-- 1
begin
for i in -5..1
loop
dbms_output.put_line(i);
end loop;
end;
Если верхняя граница равна нижней, цикл выполнится ровно один раз:
-- Выведет одну строку:
-- 1
begin
for i in 1..1
loop
dbms_output.put_line(i);
end loop;
end;
-- Выведет одну строку:
-- 1
begin
for i in 1..1
loop
dbms_output.put_line(i);
end loop;
end;
В сторону уменьшения работать не будет - тело цикла for i in 3..1
не выполнится ни разу.
Стоит также обратить внимание, что мы не объявляли переменную i
в секции declare
- она создаётся автоматически. Более того, если мы объявим переменную вне цикла for
, то после завершения цикла внешняя переменная останется без изменений:
declare
i number := 10;
begin
for i in 1..4
loop
null;
end loop;
-- Выведет 10, а не 4, как можно было бы подумать
dbms_output.put_line(i);
end;
declare
i number := 10;
begin
for i in 1..4
loop
null;
end loop;
-- Выведет 10, а не 4, как можно было бы подумать
dbms_output.put_line(i);
end;
While
Цикл while
будет работать до тех пор, пока условие, указанное после него, истинно:
declare
i number := 5;
begin
while i > 0
loop
dbms_output.put_line(i);
i := i -1;
end loop;
end;
declare
i number := 5;
begin
while i > 0
loop
dbms_output.put_line(i);
i := i -1;
end loop;
end;
Вложенные циклы
Циклы могут быть вложенными. В целом, здесь нет ничего сложного, обратим мнимание лишь на несколько моментов.
Вложенные циклы for
могут иметь счётчики с одним и тем же именем:
begin
for i in 1..2
loop
dbms_output.put_line('i1= ' || i);
for i in 1..2
loop
dbms_output.put_line('i2= ' || i);
end loop;
end loop;
end;
begin
for i in 1..2
loop
dbms_output.put_line('i1= ' || i);
for i in 1..2
loop
dbms_output.put_line('i2= ' || i);
end loop;
end loop;
end;
Выведет:
i1= 1
i2= 1
i2= 2
i1= 2
i2= 1
i2= 2
i1= 1
i2= 1
i2= 2
i1= 2
i2= 1
i2= 2
Как видно, значения счётчика из внешнего цикла не перепуталось со значением счётчика из внутреннего. Это произошло потому, что мы обращались к счётчикам внутри тела цикла, к которому они относятся. Если мы перенесём вывод счётчика внешнего цикла внутрь тела внутреннего, работать не будет:
begin
for i in 1..2
loop
for i in 1..2
loop
dbms_output.put_line('i1= ' || i);
dbms_output.put_line('i2= ' || i);
end loop;
end loop;
end;
begin
for i in 1..2
loop
for i in 1..2
loop
dbms_output.put_line('i1= ' || i);
dbms_output.put_line('i2= ' || i);
end loop;
end loop;
end;
i1= 1
i2= 1
i1= 2 -- А должно быть 1
i2= 2
i1= 1
i2= 1
i1= 2
i2= 2
i1= 1
i2= 1
i1= 2 -- А должно быть 1
i2= 2
i1= 1
i2= 1
i1= 2
i2= 2
Решить эту проблему помогают метки блоков:
begin
<<main>>
for i in 1..2
loop
<<child>>
for i in 1..2
loop
dbms_output.put_line('i1= ' || main.i);
dbms_output.put_line('i2= ' || child.i);
end loop;
end loop;
end;
begin
<<main>>
for i in 1..2
loop
<<child>>
for i in 1..2
loop
dbms_output.put_line('i1= ' || main.i);
dbms_output.put_line('i2= ' || child.i);
end loop;
end loop;
end;
Результат:
i1= 1
i2= 1
i1= 1
i2= 2
i1= 2
i2= 1
i1= 2
i2= 2
i1= 1
i2= 1
i1= 1
i2= 2
i1= 2
i2= 1
i1= 2
i2= 2
Синтаксис для обращения к переменным с использованием меток выглядит так: имя метки.переменная
. При использовании вложенных циклов часто используют отдельный вариант синтаксиса завершения цикла, чтобы было понятнее, какой именно цикл заканчивает свою работу:
begin
<<main>>
for i in 1..2
loop
<<child>>
for i in 1..2
loop
dbms_output.put_line('i1= ' || main.i);
dbms_output.put_line('i2= ' || child.i);
-- указываем имя идентификатора блока
end loop child;
end loop main;
end;
begin
<<main>>
for i in 1..2
loop
<<child>>
for i in 1..2
loop
dbms_output.put_line('i1= ' || main.i);
dbms_output.put_line('i2= ' || child.i);
-- указываем имя идентификатора блока
end loop child;
end loop main;
end;
Continue. Переход на следующую итерацию цикла
Команда continue
используется для перехода к следующей итерации цикла. Часть тела цикла после данной команды не будет выполнена.
-- Выведет пять строк с числами от 1 до 5.
-- Числа с приплюсованной двойкой не будут выведены,
-- так как их вывод находится после continue.
begin
for i in 1..5
loop
dbms_output.put_line(i);
continue;
dbms_output.put_line(i + 2);
end loop;
end;
-- Выведет пять строк с числами от 1 до 5.
-- Числа с приплюсованной двойкой не будут выведены,
-- так как их вывод находится после continue.
begin
for i in 1..5
loop
dbms_output.put_line(i);
continue;
dbms_output.put_line(i + 2);
end loop;
end;
Как и с командой exit
, есть и более удобный вариант этой команды - continue when
:
begin
for i in 1..10
loop
continue when i mod 2 = 0;
dbms_output.put_line(i);
end loop;
end;
begin
for i in 1..10
loop
continue when i mod 2 = 0;
dbms_output.put_line(i);
end loop;
end;
Результат:
1
3
5
7
9
1
3
5
7
9
В начале каждой итерации мы проверяем остаток от деления на два, и если он равен нулю, сразу переходи к следующей итерации, таким образом выводя только нечётные числа.