서브쿼리는 하나의 SQL 문장 안에 포함된 또 다른 SELECT
문을 의미함. '쿼리 속의 쿼리'라고도 하며, 복잡한 로직을 하나의 쿼리로 해결할 수 있게 해주는 강력한 기능임. 서브쿼리는 항상 괄호 ()
안에 작성되어야 함.
서브쿼리는 반환하는 행의 수와 사용되는 위치에 따라 다양하게 분류됨.
위치 | 서브쿼리 명칭 | 반환 형태 | 설명 |
---|---|---|---|
WHERE , HAVING | (일반) 서브쿼리 | 단일 행/다중 행, 단일/다중 컬럼 | 조건절에서 값 비교를 위해 사용 |
FROM | 인라인 뷰 (Inline View) | 다중 행, 다중 컬럼 | 실행 결과가 하나의 가상 테이블처럼 동작 |
SELECT | 스칼라 서브쿼리 (Scalar Subquery) | 단일 행, 단일 컬럼 (하나의 값) | 조회 결과에 새로운 컬럼을 추가하는 용도 |
WHERE
절 서브쿼리조건절에서 값 비교를 위해 가장 일반적으로 사용되는 형태임.
=
, >
, <
, <>
등 단일 값을 비교하는 연산자와 함께 사용됨./*
* -- 평균 조회수보다 높은 게시물 조회 --
* 1. 서브쿼리가 먼저 실행되어 전체 게시물의 평균 조회수(하나의 값)를 계산함.
* 2. 메인 쿼리는 이 값을 기준으로 view_count를 비교함.
*/
SELECT post_id, view_count
FROM Post
WHERE view_count > (SELECT AVG(view_count) FROM Post);
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);
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;
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;
구분 | 비연관 서브쿼리 (Non-correlated) | 연관 서브쿼리 (Correlated) |
---|---|---|
실행 방식 | 서브쿼리가 독립적으로 한 번만 실행된 후, 그 결과를 메인 쿼리에 전달함. | 메인 쿼리의 각 행이 처리될 때마다, 그 행의 값을 참조하여 반복적으로 실행됨. |
대표 예시 | WHERE col IN (SELECT ...) | SELECT 절의 스칼라 서브쿼리, EXISTS |
성능 | 일반적으로 연관 서브쿼리보다 빠름. | 메인 쿼리의 행 수만큼 반복 실행되므로 데이터 양이 많을 경우 성능 저하를 유발할 수 있음. |
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);
복잡한 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;
=
, >
), 다중 행 서브쿼리에는 다중 행 연산자(IN
, ANY
, ALL
)를 사용해야 함을 묻는 문제가 출제됨.FROM
절에 사용된 서브쿼리(인라인 뷰)는 반드시 별칭을 가져야 함. 별칭이 없는 쿼리는 문법 오류임.SELECT
절의 서브쿼리는 반드시 하나의 행, 하나의 컬럼만 반환해야 함. 여러 행을 반환하면 오류가 발생함.EXISTS
vs IN
: EXISTS
는 존재 여부만 판단하며, 연관 서브쿼리로 동작하는 경우가 많음. IN
은 실제 값 목록을 비교함. 두 쿼리의 성능 차이나 동작 방식을 묻는 문제가 나올 수 있음.JOIN
으로 대체 가능한지 고려해야 함.