서브쿼리의 활용 시나리오
단일 쿼리로 해결하기 어려운 다단계 조건 필터링이나 집계 기반 검색이 필요할 때 서브쿼리(중첩 쿼리)를 사용합니다. 외부 쿼리의 조건절에 내부 쿼리의 결과값을 동적으로 주입하여 복잡한 데이터 관계를 한 번에 해결할 수 있습니다.
예제 데이터 구조
아세 세 개의 테이블을 기준으로 실습을 진행합니다.
| member_id | member_name | dept_id | phone | gender | birth_date |
|---|---|---|---|---|---|
| M1001 | 김도진 | D05 | 010-1234-5678 | 남 | 1999-04-12 |
| M1002 | 이서연 | D04 | 010-2345-6789 | 여 | 1998-11-03 |
| M1003 | 박준혁 | D02 | 010-3456-7890 | 남 | 1997-08-21 |
학과 테이블(departments)과 성적 테이블(exam_results)이 위 회원 테이블(members)과 외래키로 연결되어 있다고 가정합니다.
기본 문법 구조
SELECT 컬럼명 FROM 메인테이블 WHERE 조건컬럼 연산자 (
SELECT 컬럼명 FROM 서브테이블 WHERE 필터조건
);
내부 쿼리가 먼저 실행되어 결과 집합을 반환하면, 외부 쿼리가 해당 값을 기준으로 최종 데이터를 추출합니다.
실전 활용 패턴
1. 단일 값 매칭 (= 연산자)
서브쿼리가 정확히 하나의 행과 열을 반환할 때 등호 연산자를 사용합니다.
-- '김도진' 회원이 소속된 학과 정보 조회
SELECT * FROM departments WHERE dept_id = (
SELECT dept_id FROM members WHERE member_name = '김도진'
);
2. 다중 값 처리 (IN / NOT IN)
내부 쿼리가 여러 행을 반환할 경우 IN을 사용하며, 제외 조건에는 NOT IN을 적용합니다.
-- '소프트웨어공학과'에 재학 중인 모든 회원 조회
SELECT * FROM members WHERE dept_id IN (
SELECT dept_id FROM departments WHERE dept_name = '소프트웨어공학과'
);
-- '경영학부' 소속이 아닌 회원 목록
SELECT * FROM members WHERE dept_id NOT IN (
SELECT dept_id FROM departments WHERE faculty = '경영학부'
);
3. 집계 함수와의 결합
평균, 최대/최소값 등을 기준으로 데이터를 필터링할 때 유용합니다.
-- 실무 점수가 전체 평균을 초과한 회원 정보
SELECT * FROM members WHERE member_id IN (
SELECT member_id FROM exam_results WHERE practical_score > (
SELECT AVG(practical_score) FROM exam_results
)
);
-- 최고 득점자 정보 추출
SELECT * FROM members WHERE member_id IN (
SELECT member_id FROM exam_results WHERE practical_score = (
SELECT MAX(practical_score) FROM exam_results
)
);
4. 그룹 기반 조건 필터링
그룹화된 통계 값을 서브쿼리에서 계산한 후 외부 조건과 비교합니다.
-- 평균 소속 인원보다 많은 인원을 보유한 학과 조회
SELECT * FROM departments WHERE dept_id IN (
SELECT dept_id FROM members GROUP BY dept_id HAVING COUNT(member_id) > (
SELECT AVG(member_count) FROM (
SELECT COUNT(member_id) AS member_count FROM members GROUP BY dept_id
) AS dept_stats
)
);
5. ANY / ALL 비교 연산
집합 내 일부 또는 전체 값과 대소 비교를 수행할 때 사용합니다.
-- 특정 회원들('M1001', 'M1002') 중 한 명보다 점수가 높은 경우
SELECT * FROM members WHERE member_id IN (
SELECT member_id FROM exam_results WHERE practical_score > ANY (
SELECT practical_score FROM exam_results WHERE member_id IN ('M1001', 'M1002')
)
);
-- 특정 회원들보다 점수가 모두 높은 경우
SELECT * FROM members WHERE member_id IN (
SELECT member_id FROM exam_results WHERE practical_score > ALL (
SELECT practical_score FROM exam_results WHERE member_id IN ('M1001', 'M1002')
)
);
6. 윈도우 함수와 행 번호 기반 페이징
ROW_NUMBER()를 활용하면 정렬 기준에 따른 순위를 매기거나 특정 구간 데이터를 추출할 수 있습니다. SQL Server 2012 이상에서는 OFFSET-FETCH 구문이 더 효율적입니다.
-- 회원 ID 기준 오름차순 순위 부여
SELECT ROW_NUMBER() OVER(ORDER BY member_id ASC) AS seq, * FROM members;
-- 총점(이론+실무) 기준 내림차순 정렬 후 4~8등 추출
SELECT * FROM (
SELECT ROW_NUMBER() OVER(ORDER BY (theory_score + practical_score) DESC) AS rank_num, *
FROM exam_results
) AS ranked_data WHERE rank_num BETWEEN 4 AND 8;
-- OFFSET-FETCH를 이용한 페이징 (3개 건너뛰고 5개 가져오기)
SELECT * FROM exam_results
ORDER BY (theory_score + practical_score) DESC
OFFSET 3 ROWS FETCH NEXT 5 ROWS ONLY;