방송대 편입 첫 학기에 데이터베이스 과목을 수강하면서 SQL을 처음 배우기 시작했다. SQL의 핵심이라고 할 수 있는 그룹 질의, 중첩 질의, 조인 질의와 같은 질의(쿼리)의 여러 종류를 배우고 있다. 이러한 질의의 원리를 좀 더 꼼꼼하게 정리할 필요를 느껴서 이 포스팅을 쓰게 되었다. 오늘은 그 중에서도 서브쿼리에 대해 최대한 자세히 알아보려고 한다.
서브쿼리, 왜 필요할까?
SQL을 쓰다보면 단일 쿼리만으로는 해결이 어려운 상황이 자주 발생할 것이다.
예를 들어, 전체 평균 점수보다 점수가 높은 학생의 명단을 조회하는 쿼리는 단일 쿼리로 작성할 수 없다.
이런 문제를 해결해주는 도구가 바로 서브쿼리(Subquery)다.
서브쿼리를 이용하면 복잡한 조건이나 계산을 한 번에 처리할 수 있어서, SQL을 더 유연하게 사용할 수 있게 된다.
서브쿼리란?
서브쿼리는 학교 교재에는 '중첩 질의'라고 번역이 되어 있지만, 일반적으로 그냥 서브쿼리라고 한다.
서브쿼리는 쿼리문에 포함된 또 다른 쿼리문을 의미한다. 즉, 하나의 쿼리 안에서 또 다른 쿼리의 결과를 가져다 쓰는 방식이다. 위에서 예로 든 전체 평균 점수보다 점수가 높은 학생의 명단을 조회하는 쿼리는 다음과 같이 쓸 수 있다.
SELECT 이름
FROM 학생
WHERE 점수 > (SELECT AVG(점수) FROM 학생);
위 쿼리의 진행 순서는 다음과 같다.
- 전체 평균 점수를 계산해서
- 각 학생의 점수와 비교하고
- 평균보다 높은 학생을 뽑음
이 순서를 뒤집어서 마지막 순서부터 바깥에서 시작해 괄호 안으로 들어가면 서브쿼리가 된다.
괄호 안의 SELECT AVG(점수)로 전체 평균 점수를 계산하는 서브쿼리이고, 이 결과가 바깥쪽에 있는 WHERE 의 조건으로 사용된다.
이렇게 한 번의 쿼리로 처리할 수 있게 해주는 것이 바로 서브쿼리이다.
🟦스칼라 서브쿼리, 다중 행 서브쿼리, 상관 서브쿼리
우선, 서브쿼리는 반환 결과의 형태와 사용 방식에 따라 다음 세 가지로 나뉜다.
1. 스칼라 서브쿼리 (Scalar Subquery)
- 하나의 값만 반환하는 서브쿼리
- 주로 = > < 와 같은 연산자와 함께 사용
- WHERE절, SELECT절 등 다양한 위치에서 사용 가능
SELECT 이름
FROM 학생
WHERE 점수 > (SELECT AVG(점수) FROM 학생);
AVG(점수)는 하나의 값(평균 점수)만 반환하는 스칼라 서브쿼리이다.
2. 다중 행 서브쿼리 (Multi-row Subquery)
- 여러 행을 반환하는 서브쿼리
- IN, ANY, ALL 같은 키워드와 함께 사용
SELECT 이름
FROM 학생
WHERE 학생번호 IN (
SELECT 학생번호
FROM 성적
WHERE 과목번호 = 101
);
위 쿼리는 과목번호 101번을 수강한 학생의 학생번호 목록을 서브쿼리로 가져오고, 그 목록에 포함된 학생의 이름을 조회한다.
3. 상관 서브쿼리 (Correlated Subquery)
- 서브쿼리 안에서 바깥 쿼리의 값을 참조
- 바깥 쿼리의 각 행마다 서브쿼리가 반복 실행됨
SELECT 이름
FROM 학생 s
WHERE 점수 > (
SELECT AVG(점수)
FROM 학생
WHERE 학과번호 = s.학과번호
);
위 쿼리는 각 학생을 하나씩 검사하면서, 그 학생의 점수가 그 학생이 속한 학과의 평균 점수보다 높은지를 비교한다.
** s는 학생 테이블에 붙인 별칭(alians)으로, 바깥 쿼리의 값을 서브쿼리에서 정확히 참조할 수 있게 해준다.
* 별칭에 관한 설명은 별도의 포스팅으로 다루기로 한다.
🟩서브쿼리의 위치별 활용법과 특징
SQL에서 서브쿼리는 WHERE, FROM, SELECT 절 등 다양한 위치에 들어갈 수 있고, 위치에 따라 하는 일과 동작 방식이 조금씩 다르다.
1. WHERE절에서의 서브쿼리
- 가장 많이 사용되는 위치
- 조건을 계산하거나 비교할 때 사용
- 서브쿼리가 반환한 값을 기준으로 바깥 쿼리가 결과를 필터링
SELECT 이름
FROM 학생
WHERE 점수 > (SELECT AVG(점수) FROM 학생);
작동 원리
- 서브쿼리 (SELECT AVG(점수))가 먼저 실행되어 전체 평균 점수를 구함
- 바깥 쿼리에서는 학생 테이블의 각 행(학생)을 순회하면서
- 점수 > 평균점수 조건을 만족하는 학생만 결과로 반환
주 용도
- “평균보다 높은”, “가장 높은 값보다 작은” 등의 기준값 비교가 필요할 때
- IN, EXISTS, ANY, ALL 등의 조건문과 함께도 자주 사용
2. FROM절에서의 서브쿼리
- 서브쿼리의 결과를 임시 테이블처럼 사용
- 반드시 별칭(alias)을 붙여야 함
- 복잡한 집계 결과나 정리된 데이터를 다시 활용할 때 유용
SELECT 부서, 평균급여
FROM (
SELECT 부서, AVG(급여) AS 평균급여
FROM 직원
GROUP BY 부서
) AS 부서평균;
작동 원리
- FROM절 안의 서브쿼리가 먼저 실행되어
→ 부서별 평균 급여를 계산한 결과 테이블 생성 - 이 결과를 부서평균이라는 테이블처럼 다루며, 바깥 쿼리에서 다시 조회
주 용도
- 중간 집계 결과를 기준으로 다시 정렬하거나 필터링할 때
- 복잡한 쿼리를 단순한 테이블처럼 나눠서 보고 싶을 때
3. SELECT절에서의 서브쿼리
- 컬럼 계산용으로 사용
- 행마다 서브쿼리가 반복 실행됨 → 데이터 양이 많을 경우 성능에 주의
- 주로 상관 서브쿼리와 함께 사용됨
SELECT 이름,
(SELECT COUNT(*)
FROM 업무
WHERE 업무.직원번호 = 직원.직원번호) AS 업무수
FROM 직원;
작동 원리
- 바깥 쿼리가 직원 테이블의 한 행을 가져올 때마다,
- 안쪽 서브쿼리가 그 직원의 직원번호를 기준으로
- 몇 건의 업무가 있는지 세어서 업무수 컬럼으로 반환
주 용도
- 각 행에 대해 부가 정보를 동적으로 계산해야 할 때
즉, “이 사람은 총 몇 건의 업무를 맡고 있을까?” 또는 “이 학생은 몇 개의 과목을 수강 중일까?”처럼 행마다 다른 부가정보가 필요한 경우
ex. “학생별 수강 과목 수”를 구하면, 학생 테이블의 각 행마다 그 학생이 수강한 과목 수를 서브쿼리로 계산하게 됨.
ex. “고객별 주문 수”는 고객 테이블에서 각 고객의 주문 테이블을 참조하여 주문 건수를 하나씩 계산하는 방식. - 반복적인 조인을 피하거나, 코드 가독성을 높이고 싶을 때도 효과적
서브쿼리 위치별 비교
위치 | 주요 용도 | 특징 | 주의점 |
WHERE절 | 조건 비교 | 서브쿼리 결과를 기준으로 행 필터링 | 스칼라 또는 다중 행 |
FROM절 | 중간 결과 재사용 | 서브커리 결과를 테이블처럼 사용 | 별칭 필수 |
SELECT절 | 컬럼 계산 | 각 행마다 결과 계산 | 상관 서브쿼리의 성능에 유의 |