중첩 루프는 다른 루프 안에서 동작하는 루프야. 면접에서 한 질문이 또 다른 꼬리 질문으로 이어지는 상황을 생각해봐. 중첩 루프의 논리는 이래: 바깥 루프가 반복을 돌고, 안쪽 루프가 바깥 루프의 각 반복마다 또 반복을 도는 거지.
예시: 곱셈표
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();
동작 논리:
- 바깥 루프가
i값을 1부터 5까지 잡아. - 각
i값마다, 안쪽 루프가j값을 1부터 5까지 돌려. - 각 단계에서
i와j값을 조합해서 곱셈 결과를 계산해. - 출력은 미니 테이블처럼 보여:
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
동작 논리:
n이 0이나 1이면 1을 반환해.n > 1이면, 함수가n - 1을 인자로 자기 자신을 호출하고, 그 값을n에 곱해.- 이렇게 호출이 "쌓였다가", 한 방향으로 풀려나가.
실전 예시: 피보나치 수
피보나치 수는 각 숫자가 앞의 두 숫자를 더한 값이야. 시퀀스는 이렇게 시작해: 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
언제 중첩 루프와 재귀를 써야 할까?
중첩 루프는 테이블 작업에 좋아:
- 두 테이블 간 값 비교할 때.
- 복잡한 데이터 조합 만들 때.
재귀는 이런 데 더 좋아:
- 시퀀스 계산(예: 팩토리얼, 피보나치 등).
- 계층 구조 다룰 때(예: 상품 카테고리 트리).
흔한 실수들
중첩 루프는 특히 큰 테이블에서 성능이 "비싸"질 수 있어. SQL의 기본 기능으로 해결할 수 없을 때만 써.
재귀를 쓸 때는 꼭 "멈추는 조건"이 명확해야 해. 없으면 함수가 무한히 호출되고, 거의 확실하게 스택 오버플로우 에러가 날 거야.
복잡한 중첩 구조는 디버깅을 어렵게 만들 수 있어. 중간 결과를 출력하려면 RAISE NOTICE를 적극적으로 써봐.
GO TO FULL VERSION