Zagnieżdżone pętle to takie pętle, które działają w środku innych pętli. Wyobraź sobie sekcję pytań na rozmowie o pracę, gdzie jedno pytanie prowadzi do kolejnych dopytań. Logika zagnieżdżonych pętli jest taka: zewnętrzna pętla robi iterację, a wewnętrzna przechodzi przez swoje wartości dla każdej iteracji tej zewnętrznej.
Przykład: tabliczka mnożenia
Napiszemy funkcję, która tworzy tabliczkę mnożenia dla liczb od 1 do 5. To klasyczny przykład, gdzie używa się zagnieżdżonych pętli:
CREATE OR REPLACE FUNCTION generate_multiplication_table()
RETURNS VOID AS $$
BEGIN
FOR i IN 1..5 LOOP -- Zewnętrzna pętla
FOR j IN 1..5 LOOP -- Wewnętrzna pętla
RAISE NOTICE '% x % = %', i, j, i * j; -- Logujemy wynik
END LOOP;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Wywołanie funkcji:
SELECT generate_multiplication_table();
Jak to działa:
- Zewnętrzna pętla przechwytuje wartości
iod 1 do 5. - Dla każdej wartości
i, wewnętrzna pętla bierze wartościjod 1 do 5. - Na każdym kroku łączymy
iij, żeby policzyć wynik mnożenia. - Wynik wygląda jak mini-tabliczka:
1 x 1 = 1
1 x 2 = 2
...
5 x 5 = 25
Praktyka: szukanie przecięć w dwóch tabelach
Teraz zróbmy bardziej "życiowy" przykład. Wyobraź sobie, że mamy dwie tabele:
students(studenci, ich imiona),courses(kursy, na które są zapisani).
Chcemy znaleźć studentów, którzy są zapisani na więcej niż jeden kurs. Użyjemy zagnieżdżonych pętli:
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;
-- Wywołanie funkcji:
SELECT * FROM find_students_with_multiple_courses();
Rekurencja
Rekurencja to sytuacja, gdy funkcja wywołuje samą siebie. To trochę jakbyś poprosił kumpla o wyjaśnienie SQL, a on kazał Ci przeczytać dokumentację, która odsyła do tego samego wykładu… Nie myl rekurencji z nieskończoną pętlą. Rekurencja zawsze ma "warunek stopu" (moment, w którym funkcja przestaje się wywoływać).
Przykład: liczenie silni liczby
Silnia liczby n to iloczyn wszystkich liczb od 1 do n. Na przykład, silnia 5 (oznaczana jako 5!) to 5 * 4 * 3 * 2 * 1 = 120. Tak to można ogarnąć rekurencyjnie:
CREATE OR REPLACE FUNCTION calculate_factorial(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
-- Warunek stopu: silnia 0 lub 1 to 1
IF n = 0 OR n = 1 THEN
RETURN 1;
END IF;
-- Rekurencyjne wywołanie funkcji
RETURN n * calculate_factorial(n - 1);
END;
$$ LANGUAGE plpgsql;
-- Wywołanie funkcji:
SELECT calculate_factorial(5); -- Wynik: 120
Jak to działa:
- Jeśli
nto 0 lub 1, zwracamy 1. - Jeśli
n > 1, funkcja wywołuje samą siebie z argumentemn - 1i mnoży to przezn. - W ten sposób wywołania się "nakładają", a potem zwijają w jedną stronę.
Praktyczny przykład: liczby Fibonacciego
Liczby Fibonacciego to ciąg liczb, gdzie każda liczba jest sumą dwóch poprzednich. Sekwencja zaczyna się tak: 0, 1, 1, 2, 3, 5, 8....
Napiszmy funkcję, która liczy n-tą liczbę w tym ciągu:
CREATE OR REPLACE FUNCTION fibonacci(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
-- Warunek stopu: pierwsze dwie liczby są znane
IF n = 0 THEN
RETURN 0;
ELSIF n = 1 THEN
RETURN 1;
END IF;
-- Rekurencyjne wywołanie funkcji
RETURN fibonacci(n - 1) + fibonacci(n - 2);
END;
$$ LANGUAGE plpgsql;
-- Wywołanie funkcji:
SELECT fibonacci(6); -- Wynik: 8
Kiedy używać zagnieżdżonych pętli i rekurencji?
Zagnieżdżone pętle są spoko do pracy z tabelami:
- Porównywanie wartości między dwiema tabelami.
- Budowanie złożonych kombinacji danych.
Rekurencja lepiej się sprawdza przy:
- Liczeniu sekwencji (np. silnie, Fibonacciego).
- Pracy z hierarchicznymi strukturami (np. drzewo kategorii produktów).
Typowe wtopy
Zagnieżdżone pętle potrafią być "drogie" wydajnościowo, szczególnie przy dużych tabelach. Używaj ich tylko wtedy, gdy nie da się osiągnąć efektu zwykłym SQL-em.
Przy rekurencji upewnij się, że masz jasno określony "warunek stopu". Bez tego złapiesz nieskończone wywołania funkcji i pewnie błąd przepełnienia stosu.
Złożone zagnieżdżone konstrukcje mogą utrudnić debugowanie. Pomagaj sobie RAISE NOTICE, żeby wyświetlać wyniki pośrednie.
GO TO FULL VERSION