JavaRush /Курси /SQL SELF /Вкладені цикли та рекурсія

Вкладені цикли та рекурсія

SQL SELF
Рівень 51 , Лекція 3
Відкрита

Вкладені цикли — це цикли, які крутяться всередині інших циклів. Уяви собі секцію питань на співбесіді, коли одне питання тягне за собою ще купу уточнень. Логіка вкладених циклів така: зовнішній цикл робить ітерацію, а внутрішній цикл проходиться для кожної ітерації зовнішнього циклу.

Приклад: таблиця множення

Напишемо функцію, яка створює таблицю множення для чисел від 1 до 5. Це класика, де юзаються вкладені цикли:

CREATE OR REPLACE FUNCTION generate_multiplication_table()
RETURNS VOID AS $$
BEGIN
  FOR i IN 1..5 LOOP -- Зовнішній цикл
    FOR j IN 1..5 LOOP -- Внутрішній цикл
      RAISE NOTICE '% x % = %', i, j, i * j; -- Логуємо результат
    END LOOP;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Виклик функції:
SELECT generate_multiplication_table();

Логіка роботи:

  1. Зовнішній цикл перебирає значення i від 1 до 5.
  2. Для кожного значення i, внутрішній цикл бере значення j від 1 до 5.
  3. На кожному кроці комбінуються значення i і j, щоб порахувати результат множення.
  4. Вивід виглядає як міні-таблиця:
   1 x 1 = 1
   1 x 2 = 2
   ...
   5 x 5 = 25

Практика: пошук перетинів у двох таблицях

Тепер давай зробимо більш "життєвий" приклад. Уяви, що в нас є дві таблиці:

  • students (студенти, їх імена),
  • courses (курси, на які вони записані).

Ми хочемо знайти студентів, які записані більше ніж на один курс. Юзаємо вкладені цикли:

CREATE OR REPLACE FUNCTION find_students_with_multiple_courses()
RETURNS TABLE(student_name TEXT, course_name TEXT) AS $$
BEGIN
  FOR student IN SELECT DISTINCT student_name FROM students LOOP
    FOR course IN SELECT DISTINCT course_name FROM courses WHERE student_id = student.student_id LOOP
      RETURN QUERY SELECT student.student_name, course.course_name;
    END LOOP;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Виклик функції:
SELECT * FROM find_students_with_multiple_courses();

Рекурсія

Рекурсія — це коли функція викликає саму себе. Це як якщо ти попросиш друга пояснити SQL, а він скаже почитати документацію, яка посилається на цей же урок… Не плутай рекурсію з нескінченним циклом. У рекурсії завжди є "умова зупинки" (точка, де функція перестає викликати саму себе).

Приклад: обчислення факторіалу числа

Факторіал числа n — це добуток усіх чисел від 1 до n. Наприклад, факторіал 5 (позначається 5!) дорівнює 5 * 4 * 3 * 2 * 1 = 120. Ось як це можна зробити через рекурсію:

CREATE OR REPLACE FUNCTION calculate_factorial(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
  -- Умова зупинки: факторіал 0 або 1 дорівнює 1
  IF n = 0 OR n = 1 THEN
    RETURN 1;
  END IF;

  -- Рекурсивний виклик функції
  RETURN n * calculate_factorial(n - 1);
END;
$$ LANGUAGE plpgsql;

-- Виклик функції:
SELECT calculate_factorial(5); -- Результат: 120

Логіка роботи:

  1. Якщо n дорівнює 0 або 1, повертаємо 1.
  2. Якщо n > 1, функція викликає саму себе з аргументом n - 1 і множить це значення на n.
  3. Таким чином, виклики "накопичуються", а потім розкручуються в один бік.

Практичний приклад: числа Фібоначчі

Числа Фібоначчі — це послідовність чисел, де кожне число є сумою двох попередніх. Послідовність починається так: 0, 1, 1, 2, 3, 5, 8....

Давай напишемо функцію для обчислення n-го числа в послідовності:

CREATE OR REPLACE FUNCTION fibonacci(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
  -- Умова зупинки: перші два числа відомі
  IF n = 0 THEN
    RETURN 0;
  ELSIF n = 1 THEN
    RETURN 1;
  END IF;

  -- Рекурсивний виклик функції
  RETURN fibonacci(n - 1) + fibonacci(n - 2);
END;
$$ LANGUAGE plpgsql;

-- Виклик функції:
SELECT fibonacci(6); -- Результат: 8

Коли юзати вкладені цикли та рекурсію?

  1. Вкладені цикли круто підходять для роботи з таблицями:

    • Порівняння значень між двома таблицями.
    • Побудова складних комбінацій даних.
  2. Рекурсія краще для:

    • Обчислення послідовностей (наприклад, факторіалів, Фібоначчі).
    • Роботи з ієрархічними структурами (наприклад, дерево категорій товарів).

Типові граблі

Вкладені цикли іноді дуже "дорогі" по продуктивності, особливо якщо працюєш з великими таблицями. Використовуй їх тільки коли не можна зробити це звичайними засобами SQL.

Коли юзаєш рекурсію, впевнись, що в тебе є чітка "умова зупинки". Без неї отримаєш нескінченний виклик функції і, скоріш за все, помилку переповнення стеку.

Складні вкладені конструкції можуть ускладнити дебаг. Допомагай собі RAISE NOTICE для виводу проміжних результатів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ