Vòng lặp lồng nhau là vòng lặp chạy bên trong một vòng lặp khác. Hãy tưởng tượng phần hỏi đáp trong phỏng vấn, khi một câu hỏi lại dẫn đến nhiều câu hỏi phụ khác. Logic của vòng lặp lồng nhau là: vòng ngoài lặp qua từng giá trị, còn vòng trong sẽ chạy cho mỗi lần lặp của vòng ngoài.
Ví dụ: bảng cửu chương
Viết function tạo bảng cửu chương cho các số từ 1 đến 5. Đây là ví dụ kinh điển dùng vòng lặp lồng nhau:
CREATE OR REPLACE FUNCTION generate_multiplication_table()
RETURNS VOID AS $$
BEGIN
FOR i IN 1..5 LOOP -- Vòng lặp ngoài
FOR j IN 1..5 LOOP -- Vòng lặp trong
RAISE NOTICE '% x % = %', i, j, i * j; -- Log kết quả
END LOOP;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Gọi function:
SELECT generate_multiplication_table();
Logic hoạt động:
- Vòng ngoài lấy giá trị
itừ 1 đến 5. - Với mỗi giá trị
i, vòng trong lấy giá trịjtừ 1 đến 5. - Mỗi bước sẽ kết hợp
ivàjđể tính kết quả nhân. - Kết quả in ra như một bảng nhỏ:
1 x 1 = 1
1 x 2 = 2
...
5 x 5 = 25
Thực hành: tìm giao nhau trong hai bảng
Giờ thử ví dụ "đời thực" hơn nhé. Giả sử có hai bảng:
students(sinh viên, tên của họ),courses(các khoá học mà họ đăng ký).
Mình muốn tìm sinh viên đăng ký nhiều hơn một khoá học. Dùng vòng lặp lồng nhau như sau:
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;
-- Gọi function:
SELECT * FROM find_students_with_multiple_courses();
Đệ quy
Đệ quy là khi function tự gọi lại chính nó. Kiểu như bạn hỏi bạn mình giải thích SQL, mà bạn ấy lại bảo đọc tài liệu, mà tài liệu lại dẫn về bài học này... Đừng nhầm đệ quy với vòng lặp vô tận nhé. Đệ quy luôn phải có "điều kiện dừng" (điểm mà function không tự gọi lại nữa).
Ví dụ: tính giai thừa của một số
Giai thừa của số n là tích của tất cả các số từ 1 đến n. Ví dụ, giai thừa của 5 (ký hiệu 5!) là 5 * 4 * 3 * 2 * 1 = 120. Đây là cách làm bằng đệ quy:
CREATE OR REPLACE FUNCTION calculate_factorial(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
-- Điều kiện dừng: giai thừa 0 hoặc 1 là 1
IF n = 0 OR n = 1 THEN
RETURN 1;
END IF;
-- Gọi đệ quy
RETURN n * calculate_factorial(n - 1);
END;
$$ LANGUAGE plpgsql;
-- Gọi function:
SELECT calculate_factorial(5); -- Kết quả: 120
Logic hoạt động:
- Nếu
nlà 0 hoặc 1, trả về 1. - Nếu
n > 1, function tự gọi lại vớin - 1và nhân vớin. - Các lần gọi sẽ "chồng chất" rồi thu gọn lại thành kết quả cuối.
Ví dụ thực tế: dãy Fibonacci
Dãy Fibonacci là dãy số mà mỗi số là tổng của hai số trước đó. Dãy bắt đầu như sau: 0, 1, 1, 2, 3, 5, 8....
Viết function tính số thứ n trong dãy:
CREATE OR REPLACE FUNCTION fibonacci(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
-- Điều kiện dừng: hai số đầu đã biết
IF n = 0 THEN
RETURN 0;
ELSIF n = 1 THEN
RETURN 1;
END IF;
-- Gọi đệ quy
RETURN fibonacci(n - 1) + fibonacci(n - 2);
END;
$$ LANGUAGE plpgsql;
-- Gọi function:
SELECT fibonacci(6); -- Kết quả: 8
Khi nào dùng vòng lặp lồng nhau và đệ quy?
Vòng lặp lồng nhau hợp cho làm việc với bảng:
- So sánh giá trị giữa hai bảng.
- Kết hợp dữ liệu phức tạp.
Đệ quy hợp cho:
- Tính dãy số (như giai thừa, Fibonacci).
- Xử lý cấu trúc phân cấp (ví dụ cây danh mục sản phẩm).
Lỗi phổ biến
Vòng lặp lồng nhau đôi khi "ngốn" tài nguyên, nhất là khi làm với bảng lớn. Chỉ dùng khi không thể giải quyết bằng SQL thông thường.
Khi dùng đệ quy, nhớ phải có "điều kiện dừng" rõ ràng. Nếu không sẽ bị gọi function vô tận và dễ gặp lỗi tràn stack.
Cấu trúc lồng nhau phức tạp sẽ khó debug. Hãy dùng RAISE NOTICE để in ra kết quả trung gian cho dễ kiểm tra.
GO TO FULL VERSION