LOOP в Oracle PL/SQL— 100 практических примеров

LOOP в Oracle PL/SQL. Введение

Есть конструкции, которые кажутся простыми, а потом внезапно оказываются очень мощными.
LOOP в Oracle — как раз из таких. Снаружи это просто цикл, а на практике — основа
для обработки курсоров, коллекций, пакетной логики, генерации тестовых данных, служебных обходов и аккуратной бизнес-автоматизации.
Я люблю LOOP за честность: ты явно управляешь потоком выполнения и понимаешь, где цикл начинается, где заканчивается и при каком условии он должен остановиться.

Синтаксис

LOOP
  -- действия
  EXIT WHEN condition;
END LOOP;

В Oracle чаще всего встречаются три формы:
LOOP с ручным выходом,
WHILE для цикла с условием на входе,
и FOR для диапазонов и курсоров.
Если нужен полный контроль — выбирай базовую форму.
Если границы понятны заранее — часто удобнее FOR.

Где используют

  • обработка строк курсора по одной
  • обход массивов и коллекций
  • генерация тестовых данных
  • пакетные обновления и сервисные процедуры
  • поиск первого подходящего значения
  • сложная логика с вложенными условиями, где одного SELECT уже мало

100 примеров

1. Бесконечный цикл с ручным выходом по счётчику

DECLARE
  v_i NUMBER := 0;
BEGIN
  LOOP
    v_i := v_i + 1;
    EXIT WHEN v_i = 5;
    DBMS_OUTPUT.PUT_LINE(v_i);
  END LOOP;
END;

2. Простой проход от 1 до 10 через счётчик

DECLARE
  v_i NUMBER := 1;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE('n=' || v_i);
    v_i := v_i + 1;
    EXIT WHEN v_i > 10;
  END LOOP;
END;

3. Сумма чисел до заданного предела

DECLARE
  v_i NUMBER := 1;
  v_sum NUMBER := 0;
BEGIN
  LOOP
    v_sum := v_sum + v_i;
    v_i := v_i + 1;
    EXIT WHEN v_i > 100;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

4. Выход по условию IF + EXIT

DECLARE
  v_i NUMBER := 1;
BEGIN
  LOOP
    IF MOD(v_i,7) = 0 THEN
      EXIT;
    END IF;
    DBMS_OUTPUT.PUT_LINE(v_i);
    v_i := v_i + 1;
  END LOOP;
END;

5. Поиск первого чётного числа больше 20

DECLARE
  v_n NUMBER := 21;
BEGIN
  LOOP
    IF MOD(v_n,2) = 0 THEN
      DBMS_OUTPUT.PUT_LINE(v_n);
      EXIT;
    END IF;
    v_n := v_n + 1;
  END LOOP;
END;

6. Генерация строки повторением символа

DECLARE
  v_i NUMBER := 1;
  v_txt VARCHAR2(100) := '';
BEGIN
  LOOP
    v_txt := v_txt || '*';
    v_i := v_i + 1;
    EXIT WHEN v_i > 10;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_txt);
END;

7. Накопление произведения значений

DECLARE
  v_i NUMBER := 1;
  v_prod NUMBER := 1;
BEGIN
  LOOP
    v_prod := v_prod * v_i;
    v_i := v_i + 1;
    EXIT WHEN v_i > 5;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_prod);
END;

8. Подсчёт количества проходов

DECLARE
  v_counter NUMBER := 0;
BEGIN
  LOOP
    v_counter := v_counter + 1;
    EXIT WHEN v_counter = 3;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('passes=' || v_counter);
END;

9. Формирование списка чисел через конкатенацию

DECLARE
  v_i NUMBER := 1;
  v_txt VARCHAR2(200) := '';
BEGIN
  LOOP
    v_txt := v_txt || v_i || ',';
    v_i := v_i + 1;
    EXIT WHEN v_i > 5;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_txt);
END;

10. Проверка условия до записи в лог

BEGIN
  LOOP
    INSERT INTO audit_events(id,created_at)
    SELECT 1,SYSDATE
    FROM DUAL;
    EXIT;
  END LOOP;
END;

11. Цикл с накоплением только нечётных значений

DECLARE
  v_i NUMBER := 1;
  v_sum NUMBER := 0;
BEGIN
  LOOP
    IF MOD(v_i,2) = 1 THEN
      v_sum := v_sum + v_i;
    END IF;
    v_i := v_i + 1;
    EXIT WHEN v_i > 10;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

12. Обратный отсчёт

DECLARE
  v_i NUMBER := 5;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE(v_i);
    v_i := v_i - 1;
    EXIT WHEN v_i = 0;
  END LOOP;
END;

13. Запись нескольких тестовых строк через повторение

DECLARE
  v_i NUMBER := 1;
BEGIN
  LOOP
    INSERT INTO temp_session(k,v)
    VALUES ('k' || v_i,'v' || v_i);
    v_i := v_i + 1;
    EXIT WHEN v_i > 3;
  END LOOP;
END;

14. Генерация случайных значений в таблицу

DECLARE
  v_i NUMBER := 1;
BEGIN
  LOOP
    INSERT INTO ratings(value)
    VALUES (TRUNC(DBMS_RANDOM.VALUE(1,6)));
    v_i := v_i + 1;
    EXIT WHEN v_i > 10;
  END LOOP;
END;

15. Условный пропуск итерации через CONTINUE

DECLARE
  v_i NUMBER := 0;
BEGIN
  LOOP
    v_i := v_i + 1;
    CONTINUE WHEN MOD(v_i,2) = 0;
    DBMS_OUTPUT.PUT_LINE(v_i);
    EXIT WHEN v_i >= 10;
  END LOOP;
END;

16. Ранняя остановка при достижении порога суммы

DECLARE
  v_i NUMBER := 1;
  v_sum NUMBER := 0;
BEGIN
  LOOP
    v_sum := v_sum + v_i;
    EXIT WHEN v_sum >= 30;
    v_i := v_i + 1;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

17. Нормализация строки посимвольно

DECLARE
  v_txt VARCHAR2(20) := 'Oracle SQL';
  v_i NUMBER := 1;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE(LOWER(SUBSTR(v_txt,v_i,1)));
    v_i := v_i + 1;
    EXIT WHEN v_i > LENGTH(v_txt);
  END LOOP;
END;

18. Поиск первого символа пробела

DECLARE
  v_txt VARCHAR2(20) := 'a b c';
  v_i NUMBER := 1;
BEGIN
  LOOP
    IF SUBSTR(v_txt,v_i,1) = ' ' THEN
      DBMS_OUTPUT.PUT_LINE(v_i);
      EXIT;
    END IF;
    v_i := v_i + 1;
  END LOOP;
END;

19. Сбор строки в верхнем регистре посимвольно

DECLARE
  v_src VARCHAR2(20) := 'oracle';
  v_dst VARCHAR2(20) := '';
  v_i NUMBER := 1;
BEGIN
  LOOP
    v_dst := v_dst || UPPER(SUBSTR(v_src,v_i,1));
    v_i := v_i + 1;
    EXIT WHEN v_i > LENGTH(v_src);
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_dst);
END;

20. Мини-генератор дат на 5 дней вперёд

DECLARE
  v_i NUMBER := 0;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE(TO_CHAR(TRUNC(SYSDATE) + v_i,'YYYY-MM-DD'));
    v_i := v_i + 1;
    EXIT WHEN v_i = 5;
  END LOOP;
END;

Еще 20 примеров

21. WHILE-цикл для последовательного роста

DECLARE
  v_i NUMBER := 1;
BEGIN
  WHILE v_i <= 5 LOOP
    DBMS_OUTPUT.PUT_LINE(v_i);
    v_i := v_i + 1;
  END LOOP;
END;

22. WHILE с накоплением суммы

DECLARE
  v_i NUMBER := 1;
  v_sum NUMBER := 0;
BEGIN
  WHILE v_i <= 10 LOOP
    v_sum := v_sum + v_i;
    v_i := v_i + 1;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

23. WHILE для обработки строки по символам

DECLARE
  v_txt VARCHAR2(20) := 'dual';
  v_i NUMBER := 1;
BEGIN
  WHILE v_i <= LENGTH(v_txt) LOOP
    DBMS_OUTPUT.PUT_LINE(SUBSTR(v_txt,v_i,1));
    v_i := v_i + 1;
  END LOOP;
END;

24. FOR по диапазону 1..5

BEGIN
  FOR i IN 1..5 LOOP
    DBMS_OUTPUT.PUT_LINE(i);
  END LOOP;
END;

25. REVERSE FOR для обратного прохода

BEGIN
  FOR i IN REVERSE 1..5 LOOP
    DBMS_OUTPUT.PUT_LINE(i);
  END LOOP;
END;

26. FOR с условием внутри

BEGIN
  FOR i IN 1..10 LOOP
    IF MOD(i,3)=0 THEN
      DBMS_OUTPUT.PUT_LINE(i);
    END IF;
  END LOOP;
END;

27. FOR и вставка 5 тестовых записей

BEGIN
  FOR i IN 1..5 LOOP
    INSERT INTO temp_cache(id)
    VALUES (i);
  END LOOP;
END;

28. FOR с расчётом квадрата числа

BEGIN
  FOR i IN 1..5 LOOP
    DBMS_OUTPUT.PUT_LINE(i * i);
  END LOOP;
END;

29. FOR для построения CSV-строки

DECLARE
  v_txt VARCHAR2(100) := '';
BEGIN
  FOR i IN 1..5 LOOP
    v_txt := v_txt || i || ',';
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_txt);
END;

30. FOR для дней месяца

BEGIN
  FOR i IN 1..7 LOOP
    DBMS_OUTPUT.PUT_LINE(TO_CHAR(TRUNC(SYSDATE,'MM') + i - 1,'DD'));
  END LOOP;
END;

31. FOR и random для генерации оценок

BEGIN
  FOR i IN 1..5 LOOP
    INSERT INTO ratings(value)
    VALUES (TRUNC(DBMS_RANDOM.VALUE(1,6)));
  END LOOP;
END;

32. FOR и DECODE для короткой маркировки

BEGIN
  FOR i IN 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE(DECODE(i,1,'A',2,'B','C'));
  END LOOP;
END;

33. FOR и CASE для классификации

BEGIN
  FOR i IN 1..6 LOOP
    DBMS_OUTPUT.PUT_LINE(CASE WHEN i<3 THEN 'low' WHEN i<5 THEN 'mid' ELSE 'high' END);
  END LOOP;
END;

34. FOR для суммирования чётных чисел

DECLARE
  v_sum NUMBER := 0;
BEGIN
  FOR i IN 1..10 LOOP
    IF MOD(i,2)=0 THEN
      v_sum := v_sum + i;
    END IF;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

35. FOR для подсчёта длины строки вручную

DECLARE
  v_txt VARCHAR2(20) := 'oracle';
  v_len NUMBER := 0;
BEGIN
  FOR i IN 1..LENGTH(v_txt) LOOP
    v_len := v_len + 1;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_len);
END;

36. FOR для генерации email-адресов

BEGIN
  FOR i IN 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE('user' || i || '@example.com');
  END LOOP;
END;

37. FOR и вставка через sequence

BEGIN
  FOR i IN 1..3 LOOP
    INSERT INTO users(user_id,email)
    VALUES (seq_orders.NEXTVAL,'x' || i || '@mail.com');
  END LOOP;
END;

38. FOR и TRUNC для генерации дат

BEGIN
  FOR i IN 0..6 LOOP
    DBMS_OUTPUT.PUT_LINE(TRUNC(SYSDATE) + i);
  END LOOP;
END;

39. FOR с REVERSE для удаления по убыванию id

BEGIN
  FOR i IN REVERSE 1..3 LOOP
    DELETE FROM temp_cache WHERE id = i;
  END LOOP;
END;

40. FOR с EXIT по условию на значении

BEGIN
  FOR i IN 1..10 LOOP
    EXIT WHEN i = 4;
    DBMS_OUTPUT.PUT_LINE(i);
  END LOOP;
END;

Еще 20 примеров

41. Курсорный проход FOR IN SELECT

BEGIN
  FOR r IN (SELECT user_id,email FROM users) LOOP
    DBMS_OUTPUT.PUT_LINE(r.email);
  END LOOP;
END;

42. Курсорный проход с фильтром WHERE

BEGIN
  FOR r IN (SELECT order_id,user_id FROM orders WHERE order_date >= TRUNC(SYSDATE)) LOOP
    DBMS_OUTPUT.PUT_LINE(r.order_id);
  END LOOP;
END;

43. Курсор по представлению с JOIN

BEGIN
  FOR r IN (
    SELECT u.email,o.order_id
    FROM users u
    JOIN orders o ON o.user_id = u.user_id
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.email || ':' || r.order_id);
  END LOOP;
END;

44. Курсор с ORDER BY для упорядоченного обхода

BEGIN
  FOR r IN (
    SELECT order_id,order_date
    FROM orders
    ORDER BY order_date DESC
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.order_id);
  END LOOP;
END;

45. Курсор и накопление общей суммы

DECLARE
  v_total NUMBER := 0;
BEGIN
  FOR r IN (SELECT amount FROM payments) LOOP
    v_total := v_total + r.amount;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_total);
END;

46. Курсор и выбор максимального значения

DECLARE
  v_max NUMBER := 0;
BEGIN
  FOR r IN (SELECT amount FROM payments) LOOP
    IF r.amount > v_max THEN
      v_max := r.amount;
    END IF;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_max);
END;

47. Курсор и пропуск пустых email через CONTINUE

BEGIN
  FOR r IN (SELECT email FROM users) LOOP
    CONTINUE WHEN r.email IS NULL;
    DBMS_OUTPUT.PUT_LINE(r.email);
  END LOOP;
END;

48. Курсор и раннее завершение после первой записи

BEGIN
  FOR r IN (SELECT user_id FROM users ORDER BY user_id) LOOP
    DBMS_OUTPUT.PUT_LINE(r.user_id);
    EXIT;
  END LOOP;
END;

49. Курсор по константному набору через UNION ALL

BEGIN
  FOR r IN (
    SELECT 'A' code FROM DUAL
    UNION ALL
    SELECT 'B' FROM DUAL
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.code);
  END LOOP;
END;

50. Курсор и UPDATE по найденным строкам

BEGIN
  FOR r IN (SELECT task_id FROM tasks WHERE status='NEW') LOOP
    UPDATE tasks
    SET status='DONE'
    WHERE task_id = r.task_id;
  END LOOP;
END;

51. Курсор и DELETE по условию

BEGIN
  FOR r IN (SELECT id FROM temp_session WHERE k IS NULL) LOOP
    DELETE FROM temp_session WHERE id = r.id;
  END LOOP;
END;

52. Курсор и INSERT в аудит

BEGIN
  FOR r IN (SELECT order_id FROM orders) LOOP
    INSERT INTO audit_events(id,created_at)
    VALUES (r.order_id,SYSDATE);
  END LOOP;
END;

53. Явный cursor OPEN FETCH EXIT WHEN NOTFOUND

DECLARE
  CURSOR c IS SELECT user_id,email FROM users;
  v_id users.user_id%TYPE;
  v_email users.email%TYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO v_id,v_email;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(v_email);
  END LOOP;
  CLOSE c;
END;

54. Явный cursor с подсчётом строк

DECLARE
  CURSOR c IS SELECT order_id FROM orders;
  v_id orders.order_id%TYPE;
  v_cnt NUMBER := 0;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO v_id;
    EXIT WHEN c%NOTFOUND;
    v_cnt := v_cnt + 1;
  END LOOP;
  CLOSE c;
  DBMS_OUTPUT.PUT_LINE(v_cnt);
END;

55. Явный cursor и поиск первого совпадения

DECLARE
  CURSOR c IS SELECT email FROM users ORDER BY user_id;
  v_email users.email%TYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO v_email;
    EXIT WHEN c%NOTFOUND;
    IF v_email LIKE '%@example.com' THEN
      DBMS_OUTPUT.PUT_LINE(v_email);
      EXIT;
    END IF;
  END LOOP;
  CLOSE c;
END;

56. Явный cursor с обработкой пустой выборки

DECLARE
  CURSOR c IS SELECT user_id FROM users WHERE 1=0;
  v_id users.user_id%TYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO v_id;
    EXIT WHEN c%NOTFOUND;
  END LOOP;
  IF c%ROWCOUNT = 0 THEN
    DBMS_OUTPUT.PUT_LINE('empty');
  END IF;
  CLOSE c;
END;

57. Явный cursor с CASE внутри обработки

DECLARE
  CURSOR c IS SELECT amount FROM payments;
  v_amount payments.amount%TYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO v_amount;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(CASE WHEN v_amount > 1000 THEN 'big' ELSE 'small' END);
  END LOOP;
  CLOSE c;
END;

58. Явный cursor + BULK-like батчи вручную

DECLARE
  CURSOR c IS SELECT order_id FROM orders;
  v_id orders.order_id%TYPE;
  v_cnt NUMBER := 0;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO v_id;
    EXIT WHEN c%NOTFOUND;
    v_cnt := v_cnt + 1;
    IF MOD(v_cnt,100) = 0 THEN
      COMMIT;
    END IF;
  END LOOP;
  CLOSE c;
END;

59. Вложенный проход по пользователям и заказам

BEGIN
  FOR u IN (SELECT user_id FROM users) LOOP
    FOR o IN (SELECT order_id FROM orders WHERE user_id = u.user_id) LOOP
      DBMS_OUTPUT.PUT_LINE(u.user_id || ':' || o.order_id);
    END LOOP;
  END LOOP;
END;

60. Метки label для внешнего EXIT

BEGIN
  <<outer_loop>>
  FOR i IN 1..3 LOOP
    FOR j IN 1..3 LOOP
      EXIT outer_loop WHEN i = 2 AND j = 2;
      DBMS_OUTPUT.PUT_LINE(i || ',' || j);
    END LOOP;
  END LOOP;
END;

Еще 20 примеров

61. Обход associative array по индексам

DECLARE
  TYPE t_map IS TABLE OF VARCHAR2(20) INDEX BY PLS_INTEGER;
  v_map t_map;
  v_i PLS_INTEGER;
BEGIN
  v_map(1) := 'A';
  v_map(2) := 'B';
  v_i := v_map.FIRST;
  LOOP
    EXIT WHEN v_i IS NULL;
    DBMS_OUTPUT.PUT_LINE(v_map(v_i));
    v_i := v_map.NEXT(v_i);
  END LOOP;
END;

62. Обход nested table через FIRST..LAST

DECLARE
  TYPE t_list IS TABLE OF NUMBER;
  v_list t_list := t_list(10,20,30);
BEGIN
  FOR i IN v_list.FIRST..v_list.LAST LOOP
    DBMS_OUTPUT.PUT_LINE(v_list(i));
  END LOOP;
END;

63. Обход varray и сумма элементов

DECLARE
  TYPE t_arr IS VARRAY(5) OF NUMBER;
  v_arr t_arr := t_arr(1,2,3,4,5);
  v_sum NUMBER := 0;
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    v_sum := v_sum + v_arr(i);
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

64. Обход sparse-массива через NEXT

DECLARE
  TYPE t_map IS TABLE OF VARCHAR2(20) INDEX BY PLS_INTEGER;
  v_map t_map;
  v_i PLS_INTEGER;
BEGIN
  v_map(10) := 'x';
  v_map(20) := 'y';
  v_i := v_map.FIRST;
  LOOP
    EXIT WHEN v_i IS NULL;
    DBMS_OUTPUT.PUT_LINE(v_i || '=' || v_map(v_i));
    v_i := v_map.NEXT(v_i);
  END LOOP;
END;

65. Удаление чётных элементов из коллекции

DECLARE
  TYPE t_list IS TABLE OF NUMBER;
  v_list t_list := t_list(1,2,3,4,5);
BEGIN
  FOR i IN REVERSE 1..v_list.COUNT LOOP
    IF MOD(v_list(i),2)=0 THEN
      v_list.DELETE(i);
    END IF;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_list.COUNT);
END;

66. Преобразование коллекции в CSV

DECLARE
  TYPE t_list IS TABLE OF VARCHAR2(10);
  v_list t_list := t_list('A','B','C');
  v_txt VARCHAR2(50) := '';
BEGIN
  FOR i IN 1..v_list.COUNT LOOP
    v_txt := v_txt || v_list(i) || ',';
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_txt);
END;

67. BULK COLLECT + обход результата

DECLARE
  TYPE t_ids IS TABLE OF users.user_id%TYPE;
  v_ids t_ids;
BEGIN
  SELECT user_id BULK COLLECT INTO v_ids FROM users;
  FOR i IN 1..v_ids.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE(v_ids(i));
  END LOOP;
END;

68. BULK COLLECT LIMIT в явном курсоре

DECLARE
  CURSOR c IS SELECT user_id FROM users;
  TYPE t_ids IS TABLE OF users.user_id%TYPE;
  v_ids t_ids;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO v_ids LIMIT 100;
    EXIT WHEN v_ids.COUNT = 0;
    FOR i IN 1..v_ids.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE(v_ids(i));
    END LOOP;
  END LOOP;
  CLOSE c;
END;

69. FORALL после подготовки коллекции

DECLARE
  TYPE t_vals IS TABLE OF NUMBER;
  v_vals t_vals := t_vals(101,102,103);
BEGIN
  FORALL i IN 1..v_vals.COUNT
    INSERT INTO temp_cache(id) VALUES (v_vals(i));
END;

70. Подготовка коллекции в цикле перед FORALL

DECLARE
  TYPE t_vals IS TABLE OF NUMBER;
  v_vals t_vals := t_vals();
BEGIN
  FOR i IN 1..5 LOOP
    v_vals.EXTEND;
    v_vals(v_vals.LAST) := i * 10;
  END LOOP;
  FORALL j IN 1..v_vals.COUNT
    INSERT INTO temp_cache(id) VALUES (v_vals(j));
END;

71. WHILE и ассоциативный массив с PREV/NEXT

DECLARE
  TYPE t_map IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  v_map t_map;
  v_i PLS_INTEGER;
BEGIN
  v_map(1) := 10;
  v_map(3) := 30;
  v_i := v_map.LAST;
  WHILE v_i IS NOT NULL LOOP
    DBMS_OUTPUT.PUT_LINE(v_map(v_i));
    v_i := v_map.PREV(v_i);
  END LOOP;
END;

72. Обход массива строк и поиск первого совпадения

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(10);
  v_arr t_arr := t_arr('red','blue','green');
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    IF v_arr(i) = 'blue' THEN
      DBMS_OUTPUT.PUT_LINE(i);
      EXIT;
    END IF;
  END LOOP;
END;

73. Удвоение значений коллекции на месте

DECLARE
  TYPE t_arr IS TABLE OF NUMBER;
  v_arr t_arr := t_arr(1,2,3);
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    v_arr(i) := v_arr(i) * 2;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_arr(3));
END;

74. Нормализация email-ов в массиве

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(50);
  v_arr t_arr := t_arr('A@MAIL.COM','B@MAIL.COM');
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    v_arr(i) := LOWER(v_arr(i));
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_arr(1));
END;

75. Фильтрация коллекции по порогу

DECLARE
  TYPE t_arr IS TABLE OF NUMBER;
  v_arr t_arr := t_arr(5,15,25);
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    CONTINUE WHEN v_arr(i) < 10;
    DBMS_OUTPUT.PUT_LINE(v_arr(i));
  END LOOP;
END;

76. Генерация вложенной коллекции чисел

DECLARE
  TYPE t_arr IS TABLE OF NUMBER;
  v_arr t_arr := t_arr();
BEGIN
  FOR i IN 1..4 LOOP
    v_arr.EXTEND;
    v_arr(i) := i * i;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_arr(4));
END;

77. Подготовка тестовых кодов в коллекции

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(10);
  v_arr t_arr := t_arr();
BEGIN
  FOR i IN 1..3 LOOP
    v_arr.EXTEND;
    v_arr(i) := 'C' || i;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_arr(2));
END;

78. Подсчёт общей длины элементов массива

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(20);
  v_arr t_arr := t_arr('aa','bbb','cccc');
  v_sum NUMBER := 0;
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    v_sum := v_sum + LENGTH(v_arr(i));
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_sum);
END;

79. Двумерная логика через вложенные циклы

BEGIN
  FOR i IN 1..3 LOOP
    FOR j IN 1..3 LOOP
      DBMS_OUTPUT.PUT_LINE(i || '*' || j || '=' || i*j);
    END LOOP;
  END LOOP;
END;

80. Матрица дат и часов

BEGIN
  FOR d IN 0..1 LOOP
    FOR h IN 0..2 LOOP
      DBMS_OUTPUT.PUT_LINE(TO_CHAR(TRUNC(SYSDATE)+d + h/24,'YYYY-MM-DD HH24:MI'));
    END LOOP;
  END LOOP;
END;

Еще 20 примеров

81. Обработка ошибок внутри цикла и продолжение

BEGIN
  FOR i IN 1..3 LOOP
    BEGIN
      DBMS_OUTPUT.PUT_LINE(10 / (2 - i));
    EXCEPTION
      WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE('zero');
    END;
  END LOOP;
END;

82. Retry-логика с ограничением попыток

DECLARE
  v_try NUMBER := 0;
BEGIN
  LOOP
    v_try := v_try + 1;
    EXIT WHEN v_try = 3;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('tries=' || v_try);
END;

83. Ожидание события с верхней границей итераций

DECLARE
  v_i NUMBER := 0;
  v_ready NUMBER := 0;
BEGIN
  LOOP
    v_i := v_i + 1;
    IF v_i = 4 THEN
      v_ready := 1;
    END IF;
    EXIT WHEN v_ready = 1 OR v_i = 10;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_ready);
END;

84. Поиск первого NULL в массиве

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(10);
  v_arr t_arr := t_arr('A',NULL,'C');
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    IF v_arr(i) IS NULL THEN
      DBMS_OUTPUT.PUT_LINE(i);
      EXIT;
    END IF;
  END LOOP;
END;

85. Обход диапазона дат по месяцам

DECLARE
  v_dt DATE := DATE '2026-01-01';
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_dt,'YYYY-MM'));
    v_dt := ADD_MONTHS(v_dt,1);
    EXIT WHEN v_dt > DATE '2026-06-01';
  END LOOP;
END;

86. Генерация рабочих дней без выходных

DECLARE
  v_dt DATE := TRUNC(SYSDATE);
  v_cnt NUMBER := 0;
BEGIN
  LOOP
    IF TO_CHAR(v_dt,'DY','NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('SAT','SUN') THEN
      DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_dt,'YYYY-MM-DD'));
      v_cnt := v_cnt + 1;
    END IF;
    v_dt := v_dt + 1;
    EXIT WHEN v_cnt = 5;
  END LOOP;
END;

87. Пошаговая пакетная очистка с COMMIT

DECLARE
  v_rows NUMBER := 1;
BEGIN
  LOOP
    DELETE FROM temp_session WHERE ROWNUM <= 100;
    v_rows := SQL%ROWCOUNT;
    COMMIT;
    EXIT WHEN v_rows = 0;
  END LOOP;
END;

88. Пакетное обновление кусками по 100 строк

DECLARE
  v_rows NUMBER := 1;
BEGIN
  LOOP
    UPDATE tasks
    SET status = 'DONE'
    WHERE task_id IN (
      SELECT task_id
      FROM tasks
      WHERE status = 'NEW'
      AND ROWNUM <= 100
    );
    v_rows := SQL%ROWCOUNT;
    COMMIT;
    EXIT WHEN v_rows = 0;
  END LOOP;
END;

89. Динамический SQL в цикле по именам таблиц

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(30);
  v_arr t_arr := t_arr('USERS','ORDERS');
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || v_arr(i);
  END LOOP;
END;

90. Динамический SQL для удаления временных объектов

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(30);
  v_arr t_arr := t_arr('TMP_A','TMP_B');
BEGIN
  FOR i IN 1..v_arr.COUNT LOOP
    BEGIN
      EXECUTE IMMEDIATE 'DROP TABLE ' || v_arr(i);
    EXCEPTION
      WHEN OTHERS THEN NULL;
    END;
  END LOOP;
END;

91. Обход списка ID и вызов процедуры

DECLARE
  TYPE t_ids IS TABLE OF NUMBER;
  v_ids t_ids := t_ids(10,20,30);
BEGIN
  FOR i IN 1..v_ids.COUNT LOOP
    write_event(v_ids(i));
  END LOOP;
END;

92. Многошаговая валидация с EXIT WHEN

DECLARE
  v_step NUMBER := 1;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE('step=' || v_step);
    EXIT WHEN v_step = 4;
    v_step := v_step + 1;
  END LOOP;
END;

93. Цикл с накоплением до первого превышения лимита

DECLARE
  v_i NUMBER := 1;
  v_sum NUMBER := 0;
BEGIN
  LOOP
    v_sum := v_sum + i;
    EXIT WHEN v_sum > 50;
    v_i := v_i + 1;
  END LOOP;
END;

94. Печать шахматного узора символами

DECLARE
  v_line VARCHAR2(20);
BEGIN
  FOR i IN 1..4 LOOP
    v_line := '';
    FOR j IN 1..4 LOOP
      v_line := v_line || CASE WHEN MOD(i+j,2)=0 THEN '#' ELSE '.' END;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE(v_line);
  END LOOP;
END;

95. Итеративный поиск степени двойки

DECLARE
  v_val NUMBER := 1;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE(v_val);
    v_val := v_val * 2;
    EXIT WHEN v_val > 64;
  END LOOP;
END;

96. Поиск первого дня месяца, который выпал на понедельник

DECLARE
  v_dt DATE := DATE '2026-01-01';
BEGIN
  LOOP
    IF TO_CHAR(v_dt,'DY','NLS_DATE_LANGUAGE=ENGLISH') = 'MON' THEN
      DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_dt,'YYYY-MM-DD'));
      EXIT;
    END IF;
    v_dt := ADD_MONTHS(v_dt,1);
  END LOOP;
END;

97. Подготовка отчётных строк за 12 месяцев

DECLARE
  v_dt DATE := TRUNC(SYSDATE,'YYYY');
BEGIN
  FOR i IN 1..12 LOOP
    INSERT INTO temp_session(k,v)
    VALUES ('month',TO_CHAR(ADD_MONTHS(v_dt,i - 1),'YYYY-MM'));
  END LOOP;
END;

98. Цикл по JSON-ключам из заранее известного набора

DECLARE
  TYPE t_arr IS TABLE OF VARCHAR2(20);
  v_keys t_arr := t_arr('id','name','status');
BEGIN
  FOR i IN 1..v_keys.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE(v_keys(i));
  END LOOP;
END;

99. Имитация polling-проверки до успеха

DECLARE
  v_try NUMBER := 0;
  v_ok NUMBER := 0;
BEGIN
  LOOP
    v_try := v_try + 1;
    IF v_try = 5 THEN
      v_ok := 1;
    END IF;
    EXIT WHEN v_ok = 1 OR v_try = 10;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_ok);
END;

100. Финальный универсальный шаблон с EXIT WHEN и журналированием

DECLARE
  v_i NUMBER := 0;
BEGIN
  LOOP
    v_i := v_i + 1;
    INSERT INTO audit_events(id,created_at)
    VALUES (v_i,SYSDATE);
    EXIT WHEN v_i = 3;
  END LOOP;
END;

Заключение

LOOP в Oracle — это не просто цикл ради цикла. Это способ аккуратно управлять логикой,
когда нужна последовательность действий, обход результата, подготовка данных или понятный контроль над потоком.
Люблю его за то, что он заставляет писать осознанно: ты видишь каждую итерацию, каждое условие выхода и каждый побочный эффект.
Если SQL — это язык данных, то PL/SQL с циклами — уже настоящая инженерия.

Официальная документация Oracle:
https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/loop-statements.html


 

🔜 Следующая статья:

KEY в Oracle SQL — как создавать связи между таблицами


 

Понравилась статья? Поделиться с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии