안녕하세요, 데이터로 비전을 찾는 '데이터비전 연구소'의 데이터비전입니다.
지난 포스팅 [SQL JOIN의 모든 것: INNER JOIN과 LEFT JOIN의 결정적 차이]를 통해, 우리는 여러 테이블에 흩어져 있는 데이터를 하나로 합치는 강력한 기술을 배웠습니다.
하지만 실무의 질문은 여기서 한 단계 더 나아갑니다.
"전체 직원의 '평균 연봉'보다 더 많은 연봉을 받는 직원들은 누구인가요?"
이 질문을 듣고, 혹시 `WHERE salary > AVG(salary)` 라고 쓰면 되지 않을까 생각하셨나요? 안타깝게도 이 쿼리는 에러를 발생시킵니다. SQL의 실행 순서상 `WHERE`절이 실행되는 시점에는 아직 전체 평균 연봉(`AVG(salary)`)이 계산되지 않았기 때문이죠.
이 문제를 해결하려면 (1) 먼저 전체 평균 연봉을 구하고, (2) 그 결과를 가지고 다시 한번 직원 목록을 조회해야 합니다. 이렇게 두 단계 이상의 복잡한 질문에 답하기 위해 '쿼리 안에 또 다른 쿼리'를 사용하는 기술이 바로 오늘 배울 서브쿼리(Subquery)입니다.
오늘 이 글에서는 서브쿼리가 언제, 왜 필요한지, 그리고 가장 많이 사용되는 3가지 유형을 예제 중심으로 A to Z, 하나하나 떠먹여 드리겠습니다. 이 글을 이해하면 여러분은 복잡한 문제도 단계별로 분해해서 해결하는 논리적인 사고방식을 갖추게 될 것입니다.
1. WHERE 절 서브쿼리: 가장 흔하고 직관적인 사용법
가장 대표적인 유형입니다. 앞서 던졌던 질문, "평균 연봉보다 많이 받는 직원 찾기"를 서브쿼리로 해결해 보겠습니다. 마치 연습장에 먼저 계산하듯, 괄호 안에 '평균 연봉을 구하는 쿼리'를 먼저 넣어주는 방식입니다.
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
어떤가요? 컴퓨터는 괄호 안의 `(SELECT AVG(salary) FROM employees)`를 가장 먼저 실행해서 하나의 값(예: 7214)을 얻어냅니다. 그리고 나서 원래 쿼리는 `WHERE salary > 7214` 와 똑같이 동작하게 됩니다. 이렇게 메인 쿼리의 조건 값으로 다른 쿼리의 결과를 사용하고 싶을 때 `WHERE` 절 서브쿼리가 아주 유용합니다.
2. FROM 절 서브쿼리: 내가 만든 테이블을 재료로! (인라인 뷰)
두 번째 유형은 `FROM` 절에 서브쿼리를 사용하는 것입니다. `FROM` 절에는 원래 테이블 이름이 오죠? 여기에 서브쿼리를 쓰면, 서브쿼리의 실행 결과를 마치 하나의 '가상 테이블'처럼 취급하여 재료로 사용할 수 있습니다. 이를 특별히 인라인 뷰(Inline View)라고 부릅니다.
예를 들어, "'부서별 평균 연봉'을 구한 다음, 그 중에서 평균 연봉이 7,500 이상인 부서만 보고 싶다"는 더 복잡한 질문에 답해봅시다.
SELECT
A.department,
A.avg_sal
FROM
(SELECT department, AVG(salary) AS avg_sal FROM employees GROUP BY department) AS A
WHERE A.avg_sal >= 7500;
조금 복잡해 보이지만 원리는 간단합니다. (1)괄호 안의 서브쿼리가 먼저 실행되어 '부서별 평균 연봉 테이블'을 임시로 만듭니다. (2)그리고 우리는 `FROM` 뒤에 이 임시 테이블을 `AS A`라는 이름으로 올려놓고, (3)여기서 `avg_sal`이 7,500 이상인 부서만 골라내는 것이죠.
❗ Tip: 인라인 뷰에는 반드시 별명(Alias)을 붙여주세요!
`FROM` 절에 서브쿼리를 사용할 때는 반드시 `AS 별명` (위 예제에서는 `AS A`) 처럼 별명을 붙여줘야 에러가 나지 않습니다. "이름 없는 테이블은 재료로 쓸 수 없다"고 기억하시면 쉽습니다.
3. SELECT 절 서브쿼리: 모든 행에 정보 추가하기 (스칼라 서브쿼리)
마지막 유형은 `SELECT` 절에 서브쿼리를 쓰는 것입니다. 이 서브쿼리는 반드시 단 하나의 값만 반환해야 해서 스칼라 서브쿼리(Scalar Subquery)라고도 불립니다.
예를 들어, "각 직원 정보 옆에 '전체 직원의 평균 연봉'을 함께 표시해서, 각 직원의 연봉이 평균과 얼마나 차이 나는지 보고 싶을 때" 유용합니다.
SELECT
name,
salary,
(SELECT AVG(salary) FROM employees) AS '전체 평균 연봉'
FROM employees;
결과를 보면 모든 직원의 행마다 '전체 평균 연봉' 값이 똑같이 붙어서 나오는 것을 볼 수 있습니다. 각 데이터가 전체와 비교해서 어떤 수준인지 파악할 때 유용한 기법입니다. (단, 데이터가 아주 많을 경우 성능이 느려질 수 있어 주의가 필요합니다.)

마무리하며: 이제 당신은 '괄호'의 의미를 이해했습니다
오늘은 SQL 중급으로 가는 관문, 서브쿼리를 함께했습니다. 이제 여러분은 복잡한 질문을 여러 단계로 나누어 생각하고, 쿼리 안에 또 다른 쿼리를 넣어 문제를 해결하는 능력을 갖추게 되었습니다.
그런데 여기서 실무의 마지막 고충이 등장합니다.
"서브쿼리 안에 또 서브쿼리가 들어가고... `SELECT ... FROM (SELECT ... FROM (SELECT ...))` 이런 식으로 괄호가 너무 많아지니 어디가 어디인지 도저히 알아볼 수가 없어요. 이걸 '괄호 지옥'이라고 부른다면서요? 좀 더 깔끔하게 정리하는 방법은 없나요?"
정확히 보셨습니다. 이 '괄호 지옥' 문제를 해결하기 위해 등장한 것이 바로 서브쿼리의 진화된 형태, WITH 구문(CTE)입니다. 복잡한 서브쿼리들에 각각 '이름'을 붙여줌으로써, 마치 글을 쓰듯 쿼리를 훨씬 더 깔끔하고 논리적으로 만들어주는 기술입니다.
다음 포스팅에서는 이 WITH 구문을 통해 지저분한 쿼리를 어떻게 우아하게 정리하는지 알아보겠습니다. 코드의 가독성을 높이고 유지보수를 쉽게 만드는, 모든 분석가의 필수 교양과목이니 절대 놓치지 마세요!
오늘 배운 서브쿼리, 어떤 유형이 가장 헷갈리셨나요? 댓글로 남겨주시면 연구소장이 직접 답변해 드리겠습니다!
'핵심 기술 튜토리얼 (Core Tech Tutorials)' 카테고리의 다른 글
| 주니어 분석가가 가장 많이 헷갈리는 INNER JOIN과 LEFT JOIN, 이 글 하나로 끝! (1) | 2025.06.16 |
|---|---|
| SQL CASE WHEN 사용법: 데이터 카테고리화 및 조건 부여 완벽 가이드 (0) | 2025.06.15 |
| 데이터 분석의 꽃, GROUP BY와 HAVING 절 완벽 이해하기 (0) | 2025.06.14 |
| SQL 집계함수 완벽 가이드: COUNT, SUM, AVG, MAX, MIN 사용법 (1) | 2025.06.12 |
| SQL 데이터 정렬과 필터링: ORDER BY, DISTINCT, LIMIT 3총사 완벽 정리 (2) | 2025.06.10 |