데이터베이스 개념노트 18편 - 서브쿼리(Subquery)
지난 글에서는 SELECT문을 가상 테이블처럼 사용할 수 있게 해주는 뷰(View)에 대해 정리했습니다.
이번 글에서는 SQLD에서 자주 출제되는 핵심 개념인 서브쿼리(Subquery)에 대해 정리해 보겠습니다.
서브쿼리는 하나의 SQL문 안에 포함된 또 다른 SELECT문입니다. 복잡한 조건을 만들거나, 다른 조회 결과를 기준으로 데이터를 조회할 때 자주 사용됩니다.
1. 서브쿼리란?
서브쿼리(Subquery)는 SQL문 안에 포함된 또 다른 SELECT문입니다.
서브쿼리는 하나의 SQL문 내부에서 사용되는 SELECT문으로, 메인쿼리의 조건이나 조회 대상에 활용된다.
서브쿼리는 보통 괄호 안에 작성합니다.
SELECT 컬럼명
FROM 테이블명
WHERE 컬럼명 = (
SELECT 컬럼명
FROM 테이블명
WHERE 조건
);
바깥쪽 SQL을 메인쿼리, 안쪽 SQL을 서브쿼리라고 부릅니다.
2. 서브쿼리가 필요한 이유
서브쿼리는 어떤 조회 결과를 다시 다른 SQL의 조건으로 사용하고 싶을 때 유용합니다.
예를 들어 전체 회원의 평균 주문금액보다 주문금액이 큰 주문만 조회하고 싶다고 해보겠습니다. 먼저 평균 주문금액을 구해야 합니다.
SELECT AVG(주문금액)
FROM 주문;
그리고 이 결과보다 큰 주문만 조회해야 합니다. 이 두 단계를 하나의 SQL로 합칠 때 서브쿼리를 사용할 수 있습니다.
SELECT 주문번호,
회원번호,
주문금액
FROM 주문
WHERE 주문금액 > (
SELECT AVG(주문금액)
FROM 주문
);
이처럼 서브쿼리는 다른 SELECT 결과를 조건으로 활용할 수 있게 해줍니다.
3. 예제 테이블
이번 글에서는 다음 회원 테이블과 주문 테이블을 기준으로 예제를 살펴보겠습니다.
회원 테이블
| 회원번호 | 회원명 | 회원등급 | 지역 |
|---|---|---|---|
| M001 | 김지훈 | 골드 | 서울 |
| M002 | 이수진 | 실버 | 부산 |
| M003 | 박민수 | 일반 | 서울 |
| M004 | 최유진 | 골드 | 대구 |
주문 테이블
| 주문번호 | 회원번호 | 상품명 | 주문금액 | 주문일자 |
|---|---|---|---|---|
| O001 | M001 | 키보드 | 30000 | 2026-01-10 |
| O002 | M001 | 마우스 | 15000 | 2026-01-12 |
| O003 | M002 | 모니터 | 250000 | 2026-01-15 |
| O004 | M003 | 의자 | 80000 | 2026-02-01 |
4. 서브쿼리의 위치
서브쿼리는 SQL문의 여러 위치에서 사용할 수 있습니다.
| 위치 | 이름 | 설명 |
|---|---|---|
| SELECT절 | 스칼라 서브쿼리 | 하나의 값을 조회 결과 컬럼처럼 사용 |
| FROM절 | 인라인 뷰 | 서브쿼리 결과를 임시 테이블처럼 사용 |
| WHERE절 | 중첩 서브쿼리 | 조건 비교에 서브쿼리 결과 사용 |
| HAVING절 | 중첩 서브쿼리 | 그룹 조건에 서브쿼리 결과 사용 |
5. WHERE절 서브쿼리
가장 자주 보는 형태는 WHERE절에서 사용하는 서브쿼리입니다.
예를 들어 회원등급이 골드인 회원의 주문만 조회해 보겠습니다.
SELECT 주문번호,
회원번호,
상품명,
주문금액
FROM 주문
WHERE 회원번호 IN (
SELECT 회원번호
FROM 회원
WHERE 회원등급 = '골드'
);
안쪽 서브쿼리는 골드 회원의 회원번호를 조회합니다. 바깥쪽 메인쿼리는 그 회원번호에 해당하는 주문만 조회합니다.
6. 단일행 서브쿼리
단일행 서브쿼리는 실행 결과가 한 행만 반환되는 서브쿼리입니다.
단일행 서브쿼리는 서브쿼리 결과가 하나의 행만 나오는 서브쿼리이다.
단일행 서브쿼리는 보통 다음 비교 연산자와 함께 사용합니다.
| 연산자 | 의미 |
|---|---|
| = | 같다 |
| <> | 같지 않다 |
| > | 크다 |
| >= | 크거나 같다 |
| < | 작다 |
| <= | 작거나 같다 |
7. 단일행 서브쿼리 예시
평균 주문금액보다 큰 주문을 조회해 보겠습니다.
SELECT 주문번호,
회원번호,
주문금액
FROM 주문
WHERE 주문금액 > (
SELECT AVG(주문금액)
FROM 주문
);
서브쿼리인 SELECT AVG(주문금액)은 하나의 평균값만 반환합니다. 따라서 단일행 서브쿼리입니다.
단일행 서브쿼리는 결과가 반드시 하나의 행이어야 합니다. 만약 서브쿼리가 여러 행을 반환하는데 = 연산자를 사용하면 오류가 발생할 수 있습니다.
8. 단일행 서브쿼리 오류 예시
다음 SQL은 오류가 발생할 수 있습니다.
SELECT *
FROM 주문
WHERE 회원번호 = (
SELECT 회원번호
FROM 회원
WHERE 지역 = '서울'
);
서울 지역 회원이 여러 명이면 서브쿼리는 여러 회원번호를 반환합니다. 그런데 = 연산자는 하나의 값과 비교할 때 사용하는 연산자입니다.
여러 값과 비교하려면 IN 같은 다중행 연산자를 사용해야 합니다.
SELECT *
FROM 주문
WHERE 회원번호 IN (
SELECT 회원번호
FROM 회원
WHERE 지역 = '서울'
);
9. 다중행 서브쿼리
다중행 서브쿼리는 실행 결과가 여러 행으로 반환되는 서브쿼리입니다.
다중행 서브쿼리는 서브쿼리 결과가 여러 행으로 나올 수 있는 서브쿼리이다.
다중행 서브쿼리는 다음 연산자와 함께 사용합니다.
| 연산자 | 설명 |
|---|---|
| IN | 서브쿼리 결과 중 하나와 일치하면 참 |
| ANY | 서브쿼리 결과 중 하나라도 조건을 만족하면 참 |
| ALL | 서브쿼리 결과 전체와 비교하여 조건을 만족해야 참 |
| EXISTS | 서브쿼리 결과가 존재하면 참 |
10. IN 연산자
IN은 서브쿼리 결과 중 하나라도 일치하는 값이 있으면 참입니다.
SELECT 회원번호,
회원명,
회원등급
FROM 회원
WHERE 회원번호 IN (
SELECT 회원번호
FROM 주문
);
위 SQL은 주문한 적이 있는 회원을 조회합니다. 주문 테이블에 존재하는 회원번호와 일치하는 회원만 결과에 나옵니다.
11. NOT IN 연산자
NOT IN은 서브쿼리 결과에 포함되지 않는 값을 조회할 때 사용합니다.
SELECT 회원번호,
회원명
FROM 회원
WHERE 회원번호 NOT IN (
SELECT 회원번호
FROM 주문
);
위 SQL은 주문한 적이 없는 회원을 조회하는 의도로 작성할 수 있습니다.
하지만 NOT IN은 서브쿼리 결과에 NULL이 포함될 때 주의해야 합니다. 서브쿼리 결과에 NULL이 있으면 예상과 다른 결과가 나올 수 있습니다.
SQLD에서는 NOT IN과 NULL의 관계를 주의해야 합니다.
12. EXISTS 연산자
EXISTS는 서브쿼리 결과가 존재하는지 확인하는 연산자입니다.
SELECT M.회원번호,
M.회원명
FROM 회원 M
WHERE EXISTS (
SELECT 1
FROM 주문 O
WHERE O.회원번호 = M.회원번호
);
위 SQL은 주문이 존재하는 회원을 조회합니다. EXISTS는 서브쿼리에서 실제 어떤 값을 반환하는지보다, 조건에 맞는 행이 존재하는지가 중요합니다.
13. NOT EXISTS 연산자
NOT EXISTS는 서브쿼리 결과가 존재하지 않을 때 참입니다.
SELECT M.회원번호,
M.회원명
FROM 회원 M
WHERE NOT EXISTS (
SELECT 1
FROM 주문 O
WHERE O.회원번호 = M.회원번호
);
위 SQL은 주문한 적이 없는 회원을 조회합니다.
NOT IN은 NULL 때문에 주의가 필요하지만, NOT EXISTS는 존재 여부를 기준으로 판단하므로 이런 상황에서 더 안전하게 사용할 수 있는 경우가 많습니다.
14. ANY 연산자
ANY는 서브쿼리 결과 중 하나라도 조건을 만족하면 참입니다. SOME과 같은 의미로 사용할 수 있습니다.
SELECT 주문번호,
주문금액
FROM 주문
WHERE 주문금액 > ANY (
SELECT 주문금액
FROM 주문
WHERE 회원번호 = 'M001'
);
위 SQL은 M001 회원의 주문금액들 중 하나보다라도 큰 주문금액을 가진 주문을 조회합니다.
예를 들어 M001의 주문금액이 15000, 30000이라면 주문금액이 15000보다 큰 행은 조건을 만족할 수 있습니다.
15. ALL 연산자
ALL은 서브쿼리 결과 전체와 비교해서 조건을 모두 만족해야 참입니다.
SELECT 주문번호,
주문금액
FROM 주문
WHERE 주문금액 > ALL (
SELECT 주문금액
FROM 주문
WHERE 회원번호 = 'M001'
);
위 SQL은 M001 회원의 모든 주문금액보다 큰 주문을 조회합니다.
예를 들어 M001의 주문금액이 15000, 30000이라면 주문금액이 30000보다 커야 조건을 만족합니다.
16. ANY와 ALL 비교
| 표현 | 의미 | 쉽게 이해하기 |
|---|---|---|
| > ANY | 결과 중 하나보다 크면 참 | 최솟값보다 크면 참 |
| > ALL | 결과 전체보다 커야 참 | 최댓값보다 커야 참 |
| < ANY | 결과 중 하나보다 작으면 참 | 최댓값보다 작으면 참 |
| < ALL | 결과 전체보다 작아야 참 | 최솟값보다 작아야 참 |
ANY와 ALL은 처음에는 헷갈릴 수 있습니다. 기준을 하나만 기억하면 좋습니다.
- ANY : 하나라도 만족하면 된다.
- ALL : 전부 만족해야 한다.
17. 다중컬럼 서브쿼리
다중컬럼 서브쿼리는 서브쿼리에서 두 개 이상의 컬럼을 반환하는 형태입니다.
예를 들어 회원번호와 주문금액 조합을 기준으로 데이터를 비교할 수 있습니다.
SELECT 회원번호,
주문번호,
주문금액
FROM 주문
WHERE (회원번호, 주문금액) IN (
SELECT 회원번호,
MAX(주문금액)
FROM 주문
GROUP BY 회원번호
);
위 SQL은 회원별 최대 주문금액에 해당하는 주문을 조회하는 예시입니다.
서브쿼리에서 회원번호와 최대 주문금액을 함께 반환하고, 메인쿼리에서 회원번호와 주문금액의 조합이 일치하는 행을 찾습니다.
18. 상관 서브쿼리
상관 서브쿼리(Correlated Subquery)는 서브쿼리가 메인쿼리의 컬럼을 참조하는 형태입니다.
상관 서브쿼리는 서브쿼리가 메인쿼리의 값을 참조하여 실행되는 서브쿼리이다.
예를 들어 회원별 평균 주문금액보다 큰 주문을 조회해 보겠습니다.
SELECT O1.회원번호,
O1.주문번호,
O1.주문금액
FROM 주문 O1
WHERE O1.주문금액 > (
SELECT AVG(O2.주문금액)
FROM 주문 O2
WHERE O2.회원번호 = O1.회원번호
);
서브쿼리 안에서 O1.회원번호를 참조하고 있습니다. O1은 메인쿼리의 주문 테이블 별칭입니다. 따라서 이 서브쿼리는 메인쿼리의 각 행과 관련되어 실행됩니다.
19. 비상관 서브쿼리
비상관 서브쿼리는 서브쿼리가 메인쿼리와 독립적으로 실행될 수 있는 형태입니다.
SELECT 주문번호,
회원번호,
주문금액
FROM 주문
WHERE 주문금액 > (
SELECT AVG(주문금액)
FROM 주문
);
위 SQL에서 서브쿼리는 메인쿼리의 컬럼을 참조하지 않습니다. 전체 주문의 평균 주문금액을 독립적으로 계산한 뒤, 메인쿼리가 그 값을 기준으로 비교합니다.
| 구분 | 특징 |
|---|---|
| 비상관 서브쿼리 | 서브쿼리가 메인쿼리와 독립적으로 실행 가능 |
| 상관 서브쿼리 | 서브쿼리가 메인쿼리의 컬럼을 참조 |
20. SELECT절 서브쿼리
SELECT절에 사용하는 서브쿼리는 보통 스칼라 서브쿼리라고 부릅니다. 스칼라 서브쿼리는 하나의 값만 반환해야 합니다.
SELECT M.회원번호,
M.회원명,
(
SELECT COUNT(*)
FROM 주문 O
WHERE O.회원번호 = M.회원번호
) AS 주문건수
FROM 회원 M;
위 SQL은 회원별 주문건수를 SELECT절에서 서브쿼리로 계산합니다.
스칼라 서브쿼리는 하나의 행, 하나의 컬럼만 반환해야 합니다. 여러 행이 반환되면 오류가 발생할 수 있습니다.
21. FROM절 서브쿼리
FROM절에 사용하는 서브쿼리는 인라인 뷰라고 합니다.
SELECT A.회원번호,
A.총주문금액
FROM (
SELECT 회원번호,
SUM(주문금액) AS 총주문금액
FROM 주문
GROUP BY 회원번호
) A
WHERE A.총주문금액 >= 100000;
괄호 안의 서브쿼리 결과를 A라는 임시 테이블처럼 사용하고 있습니다. 이처럼 FROM절의 서브쿼리는 조회 결과를 테이블처럼 다룰 수 있습니다.
22. HAVING절 서브쿼리
HAVING절에서도 서브쿼리를 사용할 수 있습니다.
예를 들어 회원별 총 주문금액을 구한 뒤, 전체 회원 평균 주문금액보다 큰 회원만 조회해 보겠습니다.
SELECT 회원번호,
SUM(주문금액) AS 총주문금액
FROM 주문
GROUP BY 회원번호
HAVING SUM(주문금액) > (
SELECT AVG(주문금액)
FROM 주문
);
HAVING절의 서브쿼리는 그룹화된 결과를 필터링하는 기준으로 사용됩니다.
23. 서브쿼리와 JOIN 비교
서브쿼리와 JOIN은 모두 여러 테이블의 데이터를 함께 활용할 때 사용할 수 있습니다. 하지만 작성 방식과 목적이 조금 다릅니다.
| 구분 | 서브쿼리 | JOIN |
|---|---|---|
| 방식 | 쿼리 안에 또 다른 SELECT문 사용 | 테이블을 직접 연결 |
| 가독성 | 조건 중심 표현에 유리 | 여러 테이블 컬럼을 함께 조회할 때 유리 |
| 사용 예 | 평균보다 큰 값, 존재 여부 확인 | 회원명과 주문정보 함께 조회 |
| 성능 | 상황에 따라 다름 | 상황에 따라 다름 |
같은 결과를 서브쿼리로도 만들 수 있고 JOIN으로도 만들 수 있는 경우가 많습니다. 어떤 방식이 더 좋은지는 SQL의 목적, 데이터량, 실행 계획에 따라 달라질 수 있습니다.
24. 서브쿼리 사용 시 주의사항
서브쿼리를 사용할 때는 다음 사항을 주의해야 합니다.
- 서브쿼리는 괄호로 감싸서 작성한다.
- 단일행 연산자에는 서브쿼리 결과가 한 행이어야 한다.
- 다중행 결과에는 IN, ANY, ALL, EXISTS 등을 사용한다.
- 스칼라 서브쿼리는 하나의 값만 반환해야 한다.
- NOT IN 사용 시 서브쿼리 결과에 NULL이 포함되면 주의해야 한다.
- 상관 서브쿼리는 메인쿼리와의 관계를 정확히 이해해야 한다.
25. 서브쿼리에서 자주 하는 실수
| 실수 | 주의할 점 |
|---|---|
| 여러 행을 반환하는 서브쿼리에 = 사용 | IN 같은 다중행 연산자를 사용해야 함 |
| 스칼라 서브쿼리가 여러 값을 반환함 | SELECT절 서브쿼리는 하나의 값만 반환해야 함 |
| NOT IN에서 NULL을 고려하지 않음 | NULL 포함 시 결과가 예상과 달라질 수 있음 |
| 상관 서브쿼리의 별칭을 잘못 사용함 | 메인쿼리와 서브쿼리의 별칭을 명확히 구분해야 함 |
| ANY와 ALL 의미를 혼동함 | ANY는 하나라도, ALL은 전부 만족해야 함 |
26. SQLD 관점에서 꼭 기억할 내용
이번 글에서 SQLD 공부를 위해 꼭 기억해야 할 내용은 다음과 같습니다.
- 서브쿼리는 SQL문 안에 포함된 SELECT문이다.
- 서브쿼리는 SELECT, FROM, WHERE, HAVING절에서 사용할 수 있다.
- FROM절의 서브쿼리는 인라인 뷰라고 한다.
- SELECT절의 서브쿼리는 스칼라 서브쿼리로, 하나의 값만 반환해야 한다.
- 단일행 서브쿼리는 =, >, < 같은 단일행 연산자와 사용한다.
- 다중행 서브쿼리는 IN, ANY, ALL, EXISTS 등과 사용한다.
- IN은 결과 중 하나와 일치하면 참이다.
- EXISTS는 서브쿼리 결과가 존재하면 참이다.
- ANY는 하나라도 조건을 만족하면 참이다.
- ALL은 모든 결과가 조건을 만족해야 참이다.
- 상관 서브쿼리는 서브쿼리가 메인쿼리의 컬럼을 참조한다.
- NOT IN 사용 시 NULL에 주의해야 한다.
27. 핵심 요약
| 개념 | 핵심 내용 |
|---|---|
| 서브쿼리 | SQL문 안에 포함된 SELECT문 |
| 메인쿼리 | 바깥쪽 SQL문 |
| 단일행 서브쿼리 | 결과가 한 행만 반환되는 서브쿼리 |
| 다중행 서브쿼리 | 결과가 여러 행으로 반환될 수 있는 서브쿼리 |
| 스칼라 서브쿼리 | SELECT절에서 하나의 값을 반환하는 서브쿼리 |
| 인라인 뷰 | FROM절에서 사용하는 서브쿼리 |
| 상관 서브쿼리 | 메인쿼리의 컬럼을 참조하는 서브쿼리 |
| IN | 서브쿼리 결과 중 하나와 일치하면 참 |
| EXISTS | 서브쿼리 결과가 존재하면 참 |
| ANY | 하나라도 조건을 만족하면 참 |
| ALL | 모든 결과가 조건을 만족해야 참 |
28. 마무리
이번 글에서는 SQL문 안에 또 다른 SELECT문을 사용하는 서브쿼리에 대해 정리했습니다.
서브쿼리는 다른 조회 결과를 조건으로 사용하거나, FROM절에서 임시 테이블처럼 사용하거나, SELECT절에서 하나의 값을 계산하는 데 활용할 수 있습니다.
SQLD에서는 단일행 서브쿼리와 다중행 서브쿼리의 차이, IN, ANY, ALL, EXISTS의 의미, 상관 서브쿼리와 비상관 서브쿼리의 차이를 잘 구분해야 합니다.
특히 여러 행을 반환하는 서브쿼리에 = 연산자를 사용하면 오류가 발생할 수 있고, NOT IN은 NULL 때문에 예상과 다른 결과가 나올 수 있으므로 주의해야 합니다.
다음 글에서는 SQLD에서 SELECT문 심화로 자주 등장하는 집합 연산자 UNION, UNION ALL, INTERSECT, MINUS에 대해 정리해 보겠습니다.

'Computer Science > Database' 카테고리의 다른 글
| [DB] 데이터베이스 개념노트 20편 - 윈도우 함수(Window Function) (0) | 2026.05.27 |
|---|---|
| [DB] 데이터베이스 개념노트 19편 - 집합 연산자 UNION, UNION ALL, INTERSECT, MINUS (0) | 2026.05.26 |
| [DB]데이터베이스 개념노트 17편 - 뷰(View) (1) | 2026.04.16 |
| [DB] 데이터베이스 개념노트 16편 - 인덱스(Index) (0) | 2026.04.11 |
| [DB] 데이터베이스 개념노트 15편 - 트랜잭션과 ACID (0) | 2026.02.13 |