본문 바로가기
CS/Database

[MySQL] SubQuery 사용

by clolee 2025. 4. 13.

📌 서브쿼리가 사용될 수 있는 위치

위치 사용 예시
SELECT 컬럼처럼 사용
FROM 인라인 뷰처럼 사용
WHERE 조건 필터로 사용
HAVING 그룹 결과 필터링
IN, EXISTS, =, <, > 등 조건 내부 값 비교

📘 서브쿼리란?

  • SELECT문 내부에 포함된 또 다른 SELECT문
  • 단일 값, 다중 값, 테이블처럼 사용 가능
  • 복잡한 조건을 처리하거나, 결과 비교, 동적 조건 처리에 유용

✅ 1. WHERE 절에서 서브쿼리 사용

✅ 1-1. 단일 값 반환 서브쿼리 (=, <, >, !=, <> 등)

SELECT name, salary
FROM employees
WHERE salary > (
    SELECT AVG(salary) FROM employees
);

📌 설명:

  • salary > 평균 급여 조건을 위해, AVG 결과값을 서브쿼리로 뽑음
  • 서브쿼리 결과가 1개 행(스칼라)*만 나올 때 사용

⚠️ 주의:

  • 결과가 2개 이상이면 "Subquery returns more than 1 row" 오류 발생
  • LIMIT 1이나 MAX, MIN으로 단일 보장

✅ 1-2. 다중 값 반환 서브쿼리 (IN, NOT IN)

SELECT name
FROM students
WHERE class_id IN (
    SELECT id FROM classes WHERE grade = 3
);

📌 설명:

  • 3학년 클래스 ID들을 뽑고,
  • 그 ID에 포함되는 학생만 조회

💡 실무 팁:

  • IN은 값이 많을 경우 성능 저하
  • NULL이 결과에 포함되면 전체 결과가 무시될 수 있음 → NOT IN 주의

✅ 1-3. 존재 여부 확인 서브쿼리 (EXISTS, NOT EXISTS)

SELECT u.id, u.name
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

📌 설명:

  • 특정 조건을 "만족하는 행이 존재하는지" 확인
  • EXISTS존재만 판단하므로 성능이 좋음

💡 실무 팁:

  • EXISTS는 중복 제거 안 함 ⇒ IN보다 빠름
  • EXISTS 내부 SELECT는 보통 SELECT 1로 최소화

✅ 2. SELECT 절에서 서브쿼리 사용

SELECT
    name,
    (SELECT COUNT(*)
     FROM orders o
     WHERE o.user_id = u.id) AS order_count
FROM users u;

📌 설명:

  • 각 사용자별로 주문 수를 함께 출력
  • 사용자 수 × 서브쿼리 실행 수 = 성능 이슈 주의

⚠️ 성능 팁:

  • 유저 수가 많으면 N회 반복 실행됨
  • JOIN + GROUP BY로 대체 가능

✅ 3. FROM 절에서 서브쿼리 사용 (인라인 뷰)

SELECT region, COUNT(*) AS user_count
FROM (
    SELECT id, region FROM users WHERE status = 'active'
) AS active_users
GROUP BY region;

📌 설명:

  • 내부 SELECT의 결과를 가상의 테이블처럼 사용
  • 서브쿼리에 별칭 필수: AS active_users

💡 장점:

  • 복잡한 중간 결과를 명확히 분리
  • 가독성 향상, 조건 분리 가능

⚠️ 성능 주의:

  • 인라인 뷰는 인덱스를 잘 활용 못할 수 있음
  • 너무 복잡한 인라인 뷰보다는 임시 테이블, CTE가 나을 수 있음

✅ 4. HAVING 절에서 서브쿼리 사용

SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
HAVING AVG(salary) > (
    SELECT AVG(salary) FROM employees
);

📌 설명:

  • 전체 평균보다 높은 부서만 필터링
  • HAVINGGROUP BY 이후의 결과를 대상으로 조건 필터링

💡 실무 팁:

  • HAVING 서브쿼리 자주 쓰임 (전체 평균, 전체 최대값 등과 비교)

✅ 5. 상관 서브쿼리 (Correlated Subquery)

SELECT name
FROM employees e
WHERE salary > (
    SELECT AVG(salary)
    FROM employees
    WHERE department = e.department
);

📌 설명:

  • 서브쿼리가 외부 쿼리의 e.department를 참조
  • 각 행마다 서브쿼리가 실행됨 → N회 반복

💡 용도:

  • 행마다 달라지는 동적 비교
  • "내 부서 평균보다 많이 받는 직원" 등

⚠️ 성능 주의:

  • 데이터 양이 많을 경우 JOIN으로 리팩토링 권장

✅ 6. CASE WHEN 안의 서브쿼리

SELECT
    name,
    CASE
        WHEN (
            SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id
        ) > 0 THEN '주문한 고객'
        ELSE '미주문 고객'
    END AS order_status
FROM users u;

📌 설명:

  • 조건 분기에 따라 서브쿼리 결과를 조건식으로 사용
  • IF, CASE, IFNULL 안에도 서브쿼리 삽입 가능

✅ 7. 실전 예제 — 최근 주문일 포함 사용자 조회

SELECT
    u.id, u.name,
    (SELECT MAX(order_date)
     FROM orders o
     WHERE o.user_id = u.id) AS last_order_date
FROM users u
WHERE u.status = 'active';

📌 설명:

  • 사용자별로 마지막 주문 날짜를 서브쿼리로 조회

💡 JOIN 대체 버전 (성능 개선):

SELECT u.id, u.name, MAX(o.order_date) AS last_order
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.id, u.name;

✅ 8. 서브쿼리 vs JOIN — 성능 비교

조건 JOIN 추천 서브쿼리 추천
결과 재사용 필요
값 1개만 비교
조건 분기
대용량 데이터 ❌ (상관 서브쿼리는 느림)

✅ 9. 성능 요약 팁

설명
SELECT 절 서브쿼리는 반복 실행됨 JOIN + GROUP BY로 대체
EXISTS는 빠르고 가볍다 IN보다 좋을 때 많음
FROM 절 서브쿼리는 가독성 좋지만 인덱스 비효율적일 수 있음 필요한 경우 뷰나 CTE 사용
상관 서브쿼리는 느림 → 리팩토링 대상 1순위

댓글