레슨 8 / 10·4개 토픽
서브쿼리와 CTE
스칼라 서브쿼리
서브쿼리는 다른 쿼리 안에 중첩된 SELECT 문입니다. 스칼라 서브쿼리는 단일 값을 반환하며, SELECT, WHERE, HAVING 절에서 사용할 수 있습니다. 상관 서브쿼리는 외부 쿼리의 각 행에 대해 실행됩니다.
sql
-- 스칼라 서브쿼리: 평균보다 비싼 상품
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);
-- SELECT 절에서 스칼라 서브쿼리
SELECT
name,
price,
price - (SELECT AVG(price) FROM products) AS diff_from_avg
FROM products
ORDER BY diff_from_avg DESC;
-- 상관 서브쿼리: 각 카테고리의 최고가 상품
SELECT p.name, p.category_id, p.price
FROM products p
WHERE p.price = (
SELECT MAX(p2.price)
FROM products p2
WHERE p2.category_id = p.category_id
);EXISTS와 IN
sql
-- IN: 주문한 적 있는 고객
SELECT name, email
FROM users
WHERE id IN (
SELECT DISTINCT user_id FROM orders
);
-- NOT IN: 주문한 적 없는 고객
SELECT name, email
FROM users
WHERE id NOT IN (
SELECT DISTINCT user_id FROM orders
WHERE user_id IS NOT NULL
);
-- EXISTS: 주문한 적 있는 고객 (대규모 데이터에서 더 효율적)
SELECT u.name, u.email
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- NOT EXISTS: 리뷰가 없는 상품
SELECT p.name
FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM reviews r WHERE r.product_id = p.id
);CTE (Common Table Expression)
CTE는 WITH 절로 정의하는 임시 결과 집합입니다. 복잡한 쿼리를 단계별로 분리하여 가독성을 높이고, 같은 서브쿼리를 여러 번 참조할 수 있습니다. 재귀 CTE는 계층 구조 데이터를 처리하는 데 유용합니다.
sql
-- 기본 CTE: 카테고리별 매출 순위
WITH category_sales AS (
SELECT
c.name AS category,
SUM(oi.quantity * oi.unit_price) AS total_sales
FROM categories c
INNER JOIN products p ON c.id = p.category_id
INNER JOIN order_items oi ON p.id = oi.product_id
GROUP BY c.name
)
SELECT
category,
total_sales,
RANK() OVER (ORDER BY total_sales DESC) AS sales_rank
FROM category_sales;
-- 여러 CTE 결합
WITH monthly_revenue AS (
SELECT
DATE_FORMAT(ordered_at, '%Y-%m') AS month,
SUM(total_amount) AS revenue
FROM orders
WHERE status = 'completed'
GROUP BY month
),
avg_revenue AS (
SELECT AVG(revenue) AS avg_rev FROM monthly_revenue
)
SELECT
m.month,
m.revenue,
a.avg_rev,
CASE WHEN m.revenue > a.avg_rev THEN '상회' ELSE '하회' END AS vs_avg
FROM monthly_revenue m, avg_revenue a
ORDER BY m.month;재귀 CTE
sql
-- 재귀 CTE: 조직도 계층 구조
WITH RECURSIVE org_tree AS (
-- 앵커 멤버: 최상위 (상사 없음)
SELECT id, name, manager_id, 1 AS depth,
CAST(name AS CHAR(500)) AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 재귀 멤버: 하위 직원
SELECT e.id, e.name, e.manager_id, t.depth + 1,
CONCAT(t.path, ' > ', e.name)
FROM employees e
INNER JOIN org_tree t ON e.manager_id = t.id
)
SELECT
CONCAT(REPEAT(' ', depth - 1), name) AS org_chart,
depth,
path
FROM org_tree
ORDER BY path;
-- 재귀 CTE: 카테고리 트리
WITH RECURSIVE cat_tree AS (
SELECT id, name, parent_id, 0 AS level
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM categories c
INNER JOIN cat_tree ct ON c.parent_id = ct.id
)
SELECT * FROM cat_tree ORDER BY level, name;- •스칼라 서브쿼리 — 단일 값을 반환, SELECT/WHERE/HAVING에서 사용
- •상관 서브쿼리 — 외부 쿼리의 각 행에 대해 실행되는 서브쿼리
- •
EXISTS— 서브쿼리에 행이 존재하면 TRUE, 대규모 데이터에서 IN보다 효율적 - •
IN— 서브쿼리 결과 집합에 포함 여부 확인 - •
WITH(CTE) — 임시 결과 집합을 정의하여 쿼리 가독성 향상 - •
WITH RECURSIVE— 재귀적으로 반복되는 CTE, 계층 데이터 처리에 필수
💡
NOT IN에 NULL 값이 포함되면 결과가 빈 집합이 될 수 있습니다. 안전하게 사용하려면 IS NOT NULL 조건을 추가하거나, NOT EXISTS를 사용하세요. 재귀 CTE에는 반드시 종료 조건이 있어야 무한 루프를 방지할 수 있습니다.