쿼리 안의 쿼리로 단계를 나눠 복잡한 조회를 푼다.
서브쿼리
하나의 질문이 "먼저 A를 구하고, 그 결과로 B를 구한다"처럼 두 단계로 나뉠 때가 있습니다. 이때 쿼리 안에 또 다른 쿼리를 중첩하는 것을 서브쿼리라고 합니다. 조인으로 풀기 어려운 조건을 깔끔하게 표현할 수 있습니다.
학습 목표
WHERE절에서 서브쿼리로 조건을 만듭니다.IN과EXISTS로 존재 여부를 검사합니다.- 스칼라 서브쿼리로 단일 값을 끌어옵니다.
FROM절의 파생 테이블을 활용합니다.
WHERE 절 서브쿼리
-- 평균 나이보다 많은 사용자
SELECT name, age
FROM users
WHERE age > (SELECT AVG(age) FROM users);
괄호 안 서브쿼리가 먼저 실행되어 평균 나이(하나의 값)를 반환하고, 바깥 쿼리가 그 값과 비교합니다. 이렇게 값 하나를 돌려주는 서브쿼리를 스칼라 서브쿼리라고 합니다.
IN — 목록 안에 있는지
-- 한 번이라도 주문한 사용자
SELECT name
FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);
서브쿼리가 여러 값을 반환하면 IN 으로 그 목록과 비교합니다.
-- 10만 원 이상 주문이 있는 사용자만
SELECT name
FROM users
WHERE id IN (
SELECT user_id FROM orders WHERE amount >= 100000
);
EXISTS — 존재하기만 하면
SELECT u.name
FROM users AS u
WHERE EXISTS (
SELECT 1 FROM orders AS o WHERE o.user_id = u.id
);
EXISTS 는 서브쿼리에 행이 하나라도 있으면 참입니다. 바깥 행의 값(u.id)을 서브쿼리가 참조하는 형태를 상관 서브쿼리라고 하며, 행마다 평가됩니다.
💡 TIP — "존재하는지"만 따질 때는
IN보다EXISTS가 유리한 경우가 많습니다. 일치하는 첫 행을 찾으면 바로 멈추기 때문입니다. 반대로 서브쿼리 결과에NULL이 섞이면NOT IN은 예상과 다르게 동작할 수 있어, 이럴 땐NOT EXISTS가 안전합니다.
스칼라 서브쿼리를 SELECT 에서
SELECT
u.name,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS 주문수
FROM users AS u;
SELECT 절에 단일 값 서브쿼리를 넣어 각 행마다 계산된 값을 컬럼처럼 붙일 수 있습니다.
⚠️ 주의 — 스칼라 서브쿼리는 반드시 값 하나(1행 1열)만 반환해야 합니다. 여러 행이 나오면 오류가 납니다. 또한 행마다 실행되므로 데이터가 많으면 조인보다 느려질 수 있습니다.
FROM 절 파생 테이블
서브쿼리 결과를 임시 테이블처럼 다뤄 다시 조회할 수 있습니다. 반드시 별칭이 필요합니다.
-- 사용자별 총액을 먼저 구한 뒤, 그중 상위만
SELECT t.user_id, t.총액
FROM (
SELECT user_id, SUM(amount) AS 총액
FROM orders
GROUP BY user_id
) AS t
WHERE t.총액 >= 500000
ORDER BY t.총액 DESC;
집계 결과를 한 번 더 거르거나 조인할 때 자주 쓰는 패턴입니다.
요약
- 서브쿼리는 쿼리 안의 쿼리로, 단계를 나눠 문제를 풉니다.
- 단일 값은 스칼라 서브쿼리, 여러 값은
IN, 존재 여부는EXISTS로 검사합니다. - 상관 서브쿼리는 바깥 행을 참조하며 행마다 평가됩니다.
FROM절 파생 테이블은 중간 결과를 임시 테이블처럼 활용하며 별칭이 필요합니다.
연습문제
- 전체 주문 평균 금액보다 큰 금액의 주문만 조회하세요.
- 주문 이력이 있는 사용자의 이름을
IN으로 구하세요. - 각 사용자 옆에 그 사람의 주문 건수를 붙여 출력하세요.
- 사용자별 총 주문액을 구한 뒤, 총액 30만 원 이상인 사용자만 보여 주세요(파생 테이블).
힌트 — 1번은 스칼라 서브쿼리, 2번은
IN (SELECT ...), 3번은SELECT절 서브쿼리, 4번은FROM (SELECT ...)입니다.
💡 연습문제 풀이
불러오는 중…
댓글 0
“MySQL” 강좌에 대한 댓글입니다.