[SQLD] 08. 서브쿼리 (Subquery)

TJK·2025년 7월 30일
0

서브쿼리 (Subquery)

서브쿼리는 하나의 SQL 문장 안에 포함된 또 다른 SELECT을 의미함. '쿼리 속의 쿼리'라고도 하며, 복잡한 로직을 하나의 쿼리로 해결할 수 있게 해주는 강력한 기능임. 서브쿼리는 항상 괄호 () 안에 작성되어야 함.


1. 서브쿼리의 종류 및 위치

서브쿼리는 반환하는 행의 수와 사용되는 위치에 따라 다양하게 분류됨.

위치서브쿼리 명칭반환 형태설명
WHERE, HAVING(일반) 서브쿼리단일 행/다중 행, 단일/다중 컬럼조건절에서 값 비교를 위해 사용
FROM인라인 뷰 (Inline View)다중 행, 다중 컬럼실행 결과가 하나의 가상 테이블처럼 동작
SELECT스칼라 서브쿼리 (Scalar Subquery)단일 행, 단일 컬럼 (하나의 값)조회 결과에 새로운 컬럼을 추가하는 용도

2. WHERE 절 서브쿼리

조건절에서 값 비교를 위해 가장 일반적으로 사용되는 형태임.

2.1 단일 행 서브쿼리 (Single-Row Subquery)

  • 서브쿼리의 실행 결과가 오직 하나의 행, 하나의 컬럼 (하나의 값)인 경우.
  • = , > , < , <> 등 단일 값을 비교하는 연산자와 함께 사용됨.
/*
 * -- 평균 조회수보다 높은 게시물 조회 --
 * 1. 서브쿼리가 먼저 실행되어 전체 게시물의 평균 조회수(하나의 값)를 계산함.
 * 2. 메인 쿼리는 이 값을 기준으로 view_count를 비교함.
 */
SELECT post_id, view_count
FROM Post
WHERE view_count > (SELECT AVG(view_count) FROM Post);

2.2 다중 행 서브쿼리 (Multi-Row Subquery)

  • 서브쿼리의 실행 결과가 여러 개의 행을 반환하는 경우.
  • IN, ANY, ALL, EXISTS 등 다중 값을 처리할 수 있는 연산자와 함께 사용해야 함.
연산자설명
IN서브쿼리 결과 목록에 있는 값 중 하나라도 일치하면 TRUE
ANY서브쿼리 결과 중 하나라도 조건을 만족하면 TRUE (예: > ANY는 최솟값보다 크면 TRUE)
ALL서브쿼리 결과의 모든 값을 만족하면 TRUE (예: > ALL은 최댓값보다 크면 TRUE)
/*
 * -- 특정 그룹의 사용자가 작성한 게시물 조회 --
 * 1. 서브쿼리가 먼저 실행되어 manager_id가 1인 사용자들의 user_id 목록(여러 행)을 반환함.
 * 2. 메인 쿼리는 IN 연산자를 사용하여 이 목록에 포함된 user_id를 가진 게시물을 조회함.
 */
SELECT post_id, content
FROM Post
WHERE user_id IN (SELECT user_id FROM User WHERE manager_id = 1);

3. FROM 절 서브쿼리 (인라인 뷰)

FROM 절에 사용되는 서브쿼리로, 실행 결과가 마치 하나의 가상 테이블처럼 동작함. 인라인 뷰는 반드시 별칭(Alias)을 가져야 함. GROUP BY 결과를 다른 테이블과 조인하는 등 복잡한 데이터 집합을 생성할 때 매우 유용함.

/*
 * -- 사용자별 게시물 수를 포함하여 사용자 정보 조회 --
 * 1. 인라인 뷰(post_info)가 먼저 실행되어 사용자별 게시물 수 요약 테이블을 생성함.
 * 2. 메인 쿼리는 이 가상 테이블(post_info)을 실제 테이블처럼 User 테이블과 조인함.
 */
SELECT
    u.name,
    post_info.post_count
FROM
    User u
JOIN
    (SELECT user_id, COUNT(*) AS post_count
     FROM Post
     GROUP BY user_id) post_info -- 인라인 뷰에는 반드시 별칭이 필요함
ON
    u.user_id = post_info.user_id;

4. SELECT 절 서브쿼리 (스칼라 서브쿼리)

SELECT 절에 사용되며, 반드시 하나의 값(단일 행, 단일 컬럼)만을 반환해야 함. 메인 쿼리의 각 행마다 독립적으로 값을 계산하여 새로운 컬럼을 동적으로 생성하는 효과를 줌.

/*
 * -- 게시물별 좋아요 수와 댓글 수를 함께 조회 --
 * 메인 쿼리가 Post 테이블의 각 행(p)을 처리할 때마다,
 * 해당 post_id를 참조하는 스칼라 서브쿼리가 실행되어 좋아요 수와 댓글 수를 계산함.
 */
SELECT
    p.post_id,
    p.content,
    (SELECT COUNT(*) FROM Likes l WHERE l.post_id = p.post_id) AS like_count,
    (SELECT COUNT(*) FROM Comments c WHERE c.post_id = p.post_id) AS comment_count
FROM
    Post p;

5. 연관 vs 비연관 서브쿼리

구분비연관 서브쿼리 (Non-correlated)연관 서브쿼리 (Correlated)
실행 방식서브쿼리가 독립적으로 한 번만 실행된 후, 그 결과를 메인 쿼리에 전달함.메인 쿼리의 각 행이 처리될 때마다, 그 행의 값을 참조하여 반복적으로 실행됨.
대표 예시WHERE col IN (SELECT ...)SELECT 절의 스칼라 서브쿼리, EXISTS
성능일반적으로 연관 서브쿼리보다 빠름.메인 쿼리의 행 수만큼 반복 실행되므로 데이터 양이 많을 경우 성능 저하를 유발할 수 있음.

6. EXISTS 연산자

서브쿼리의 결과가 존재하는지 여부(TRUE/FALSE)만 판단하는 연산자.

  • 값을 직접 비교하지 않고 존재 여부만 체크하므로, 서브쿼리의 SELECT 절에는 SELECT 1, SELECT '* 등 어떤 것을 써도 무방함.
  • 연관 서브쿼리와 함께 사용되어, 메인 쿼리의 특정 행과 관련된 데이터가 다른 테이블에 있는지 확인할 때 효율적임.
  • NOT EXISTS는 결과가 존재하지 않을 때 TRUE를 반환함.
/*
 * -- 게시물을 한 번이라도 작성한 사용자 조회 --
 * User 테이블의 각 사용자(u)에 대해, Post 테이블에 해당 user_id가 존재하는지 체크함.
 * 존재하면 EXISTS는 TRUE를 반환하여 결과에 포함됨.
 */
SELECT u.name
FROM User u
WHERE EXISTS (SELECT 1 FROM Post p WHERE p.user_id = u.user_id);

7. 뷰 (VIEW)

복잡한 SELECT 쿼리문을 데이터베이스에 가상 테이블처럼 저장하는 객체.

  • 장점:
    • 복잡성 은닉: 긴 쿼리를 단순한 SELECT 문으로 대체 가능.
    • 보안: 사용자에게 원본 테이블 대신 필요한 컬럼만 노출.
    • 재사용성: 자주 사용하는 쿼리를 저장하여 반복 작업을 줄임.
  • VIEW는 실제 데이터를 저장하는 것이 아니라, 쿼리 정의만을 저장함. VIEW를 조회할 때마다 저장된 쿼리가 실행됨.
-- 사용자별 총 좋아요 수를 계산하는 복잡한 쿼리를 VIEW로 생성
CREATE VIEW V_USER_LIKE_COUNT AS
SELECT
    u.user_id,
    u.name,
    COUNT(l.like_id) AS total_likes
FROM
    User u
LEFT JOIN Post p ON u.user_id = p.user_id
LEFT JOIN Likes l ON p.post_id = l.post_id
GROUP BY
    u.user_id, u.name;

-- VIEW 사용: 이제 간단한 쿼리로 조회가 가능함
SELECT name, total_likes FROM V_USER_LIKE_COUNT ORDER BY total_likes DESC;

8. 시험 문제 유형 및 함정 포인트

  • 연산자 짝 맞추기: 단일 행 서브쿼리에는 단일 행 연산자(=, >), 다중 행 서브쿼리에는 다중 행 연산자(IN, ANY, ALL)를 사용해야 함을 묻는 문제가 출제됨.
  • 인라인 뷰의 별칭: FROM 절에 사용된 서브쿼리(인라인 뷰)는 반드시 별칭을 가져야 함. 별칭이 없는 쿼리는 문법 오류임.
  • 스칼라 서브쿼리의 조건: SELECT 절의 서브쿼리는 반드시 하나의 행, 하나의 컬럼만 반환해야 함. 여러 행을 반환하면 오류가 발생함.
  • EXISTS vs IN: EXISTS는 존재 여부만 판단하며, 연관 서브쿼리로 동작하는 경우가 많음. IN은 실제 값 목록을 비교함. 두 쿼리의 성능 차이나 동작 방식을 묻는 문제가 나올 수 있음.
  • 연관 서브쿼리 성능: 메인 쿼리의 행 수만큼 반복 실행될 수 있어 대용량 데이터에서 성능이 저하될 수 있다는 점을 이해해야 함. JOIN으로 대체 가능한지 고려해야 함.
profile
Hello world!

0개의 댓글