서브쿼리 (Subquery)

핵심 인사이트 (3줄 요약)

  1. 본질: 서브쿼리 (Subquery)는 SELECT, INSERT, UPDATE, DELETE 문 안에 중첩된另一 SELECT 문으로, 메인 쿼리의 조건에 필요한 값을 동적으로 산출한다.
  2. 가치: 서브쿼리를 통해 복잡한 쿼리를 단계적으로 구성하고, WHERE 조건에 미리 계산된 값을 利用할 수 있어 쿼리의 Modularization과 可読性를 높인다.
  3. 융합: 상관 서브쿼리 (Correlated Subquery)는 외부 쿼리의 행마다 내부 쿼리가 실행되어,窓関数와类似的 분석 기능을 제공한다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

개념 정의

서브쿼리 (Subquery)는 다른 SQL 문 안에 포함된 SELECT 문으로, 메인 쿼리의 실행 전이나 후에 독립적으로 실행되어 그 결과를 메인 쿼리에 제공한다.

서브쿼리가 필요한 이유

┌─────────────────────────────────────────────────────────────────────┐
│                    서브쿼리 필요성 예시                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   [예시: 평균 나이보다 나이 많은 고객 찾기]                            │
│                                                                     │
│   ① 먼저 평균 나이 계산:                                             │
│      SELECT AVG(age) FROM customers;  → 결과: 35                   │
│                                                                     │
│   ② 그 결과로 필터링:                                               │
│      SELECT name FROM customers WHERE age > 35;                      │
│                                                                     │
│   → 두 단계를 하나로 결합:                                           │
│                                                                     │
│   SELECT name                                                       │
│   FROM customers                                                     │
│   WHERE age > (SELECT AVG(age) FROM customers);                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 평균 나이를 수동으로 计算한 후 그 값을 쿼리에 하드코딩하면, 데이터가 변경될 때마다 수동으로 값을 갱신해야 한다. 서브쿼리를 사용하면 평균 나이가动态적으로 计算되어 항상 정확한 결과를 반환한다.

비유

서브쿼리는 음식점에서 "이 가게에서 제일 비싼 메뉴보다 더 비싼 메뉴"를 찾는 것과 같다. 먼저 가게에서 제일 비싼 메뉴의 가격을 찾고 (서브쿼리), 그 가격보다 더 비싼 메뉴를 필터링 (메인 쿼리)한다.

  • **📢 섹션 요약 비유:**레시피에서 "양파를 먼저 썰어서" (서브쿼리), "양파를 넣고 볶아서" (메인 쿼리) 요리를 완성하는 것과 같습니다.

Ⅱ. 서브쿼리의 유형

1. 스칼라 서브쿼리 (Scalar Subquery)

단일 값 (1행 1열)을 반환하는 서브쿼리로, 표현식이 사용될 수 있는 곳ならどこ都可以配置한다.

-- 각 고객의 나이와 평균 나이 차이
SELECT name, age,
    age - (SELECT AVG(age) FROM customers) as age_diff
FROM customers;

2. 인라인 뷰 (Inline View)

FROM 절에 사용되는 서브쿼리로, 임시 뷰를 생성한다.

-- 도시별 고객 수를 계산한 후, 2명 이상인 도시만 필터링
SELECT city, cnt
FROM (
    SELECT city, COUNT(*) as cnt
    FROM customers
    GROUP BY city
) city_counts
WHERE cnt >= 2;

3. 중첩 서브쿼리 (Nested Subquery)

WHERE 절에 사용되는 서브쿼리로, 조건 비교에 사용된다.

-- 주문이 있는 고객 목록
SELECT name
FROM customers
WHERE customer_id IN (
    SELECT customer_id
    FROM orders
);

Ⅲ. 상관 vs 비상관 서브쿼리

비상관 서브쿼리 (Non-Correlated Subquery)

서브쿼리가 메인 쿼리의 값에 의존하지 않고 독립적으로 실행된다.

-- 평균 나이보다 많은 고객 (비상관)
SELECT name, age
FROM customers
WHERE age > (SELECT AVG(age) FROM customers);

상관 서브쿼리 (Correlated Subquery)

서브쿼리가 메인 쿼리의 열을 참조하여, 메인 쿼리의 각 행마다 서브쿼리가 실행된다.

-- 각 고객의 주문 수
SELECT name,
    (SELECT COUNT(*)
     FROM orders o
     WHERE o.customer_id = c.customer_id) as order_count
FROM customers c;
┌─────────────────────────────────────────────────────────────────────┐
│                    상관 서브쿼리 동작 방식                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   customers:                                                        │
│   ┌────────┬──────┐                                                │
│   │ C001   │ 김철수│                                                │
│   │ C002   │ 이영희│                                                │
│   │ C003   │ 박민수│                                                │
│   └────────┴──────┘                                                │
│                                                                     │
│   orders:                                                           │
│   ┌────────┬──────┐                                                │
│   │ O001   │ C001 │                                                │
│   │ O002   │ C002 │                                                │
│   │ O003   │ C001 │                                                │
│   └────────┴──────┘                                                │
│                                                                     │
│   Query:                                                            │
│   SELECT c.name,                                                   │
│       (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.customer_id) │
│   FROM customers c;                                                │
│                                                                     │
│   Result:                                                           │
│   ┌──────┬────────────┐                                            │
│   │ 김철수│ 2          │ ← C001는 2건 주문                          │
│   │ 이영희│ 1          │ ← C002는 1건 주문                          │
│   │ 박민수│ 0          │ ← C003는 주문 없음                          │
│   └──────┴────────────┘                                            │
│                                                                     │
│   ※ 상관 서브쿼리는 외부 쿼리의 각 행마다 실행됨                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Ⅳ. EXISTS와 NOT EXISTS

EXISTS

서브쿼 결과가 한 건이라도 있으면 TRUE를 반환한다.

-- 주문이 있는 고객만
SELECT name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);

NOT EXISTS

서브쿼리 결과가 없으면 TRUE를 반환한다.

-- 주문이 없는 고객만
SELECT name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);
┌─────────────────────────────────────────────────────────────────────┐
│                    EXISTS 동작 예시                                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   [customers 중 주문이 있는 고객] → EXISTS 사용                        │
│   → 서브쿼리 결과가 1건 이상이면 TRUE                               │
│                                                                     │
│   [customers 중 주문이 없는 고객] → NOT EXISTS 사용                    │
│   → 서브쿼리 결과가 0건이면 TRUE                                   │
│                                                                     │
│   ※ IN vs EXISTS:                                                  │
│   • IN: 서브쿼리 결과 전체를 비교                                    │
│   • EXISTS: 행 존재 여부만 확인 (더 효율적일 수 있음)                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Ⅴ. 실무 적용 및 기술사적 판단

실무 시나리오

  1. 시나리오 — 연간 최다 주문 고객: 주문 금액을 年度別로 分析하여 최다 주문 고객을 찾을 때, 서브쿼리로 年度別 합계를 계산한 후 순위를 매긴다.

  2. 시나리오 — 전월 대비 매출 감소 확인: 서브쿼리로 전월 매출을 계산하고, 현재 월 매출과 비교한다.

도입 체크리스트

  • 기술적: 서브쿼리 결과를 캐싱하여 반복 실행을 방지했는가? 상관 서브쿼리의 성능 영향을 평가했는가?
  • 운영·보안적: 서브쿼리에 사용자 입력이 직접 포함되지 않도록 주의했는가 (SQL Injection 방지)?

안티패턴

  • 과도한 서브쿼리 중첩: 3단계 이상의 중첩은 可読性와 성능을 해친다. JOIN이나 CTE (Common Table Expression)로 대체하는 것이 좋다.

  • 상관 서브쿼리 남용: 대량 데이터에서 상관 서브쿼리는 성능 저하의 주요 원인이 된다.

  • **📢 섹션 요약 비유:**친구에게 물어보고, 그 친구의 친구에게 또 물어보고...하면 (상관 서브쿼리) 시간이 오래 걸리듯이, 서브쿼리도 깊어지면性能가 떨어집니다.


Ⅴ. 기대효과 및 결론

정량/정성 기대효과

구분단일 쿼리서브쿼리 활용개선 효과
정성하드코딩된 값동적 계산정확성 향상
정성복잡한 쿼리단계적 구성가독성·유지보수성 향상

미래 전망

  • CTE (Common Table Expression): WITH 절을利用한 서브쿼리의 가독성 향상

  • 재귀 CTE: 계층 구조 (조직도, 카테고리 트리) 탐색

  • **📢 섹션 요약 비유:**요리할 때 재료를 미리 손질하면 (서브쿼리) 조리가 쉬워지는 것처럼, 서브쿼리를利用하면 복잡한 데이터 조작도 단계별로 쉽게 구성할 수 있습니다.


📌 관련 개념 맵 (Knowledge Graph)

개념 명칭관계 및 시너지 설명
CTECommon Table Expression으로, 서브쿼리에 이름을 부여하여 가독성을 높인다.
JOIN서브쿼리와 비교되며, 때로는 상호 대체 가능하다.
윈도우 함수상관 서브쿼리와类似的 기능を提供하지만,より 효율적인 경우 많다.
스칼라 값단일 행, 단일 열의 결과로, 표현식 어디에나 사용할 수 있다.

👶 어린이를 위한 3줄 비유 설명

  1. 서브쿼리는 **"더하기 문제를 풀기 전에 먼저 빼기 문제를 푸는 것"**과 같아요. 작은 문제를 먼저 풀고 (서브쿼리), 그 결과를 큰 문제에 써먹는 거예요.
  2. 상관 서브쿼리는 **"반에서 각 친구마다 그 친구 친구 수를 물어보는 것"**과 같아요. 친구마다 물어보는 게 다르니까 (상관), 더 오래 걸려요.
  3. 서브쿼리를 잘 쓰면 복잡한 질문도 쉽게 나눠서 대답할 수 있어요!