모던지 / SQL 개발자 / SQL 기본 및 활용 8강. 서브쿼리

SQL 기본 및 활용 8강. 서브쿼리

서브쿼리를 어디에 놓았는지가 그 쿼리의 성격을 결정합니다. WHERE에 놓으면 조건이고, FROM에 놓으면 테이블이고, SELECT에 놓으면 컬럼입니다.

풀고 시작

문제 1. 여러 행을 돌려주는 서브쿼리를 단일행 비교 연산자와 함께 쓰면 어떻게 될까요?
등호 같은 단일행 연산자는 값 하나와만 비교할 수 있습니다. 서브쿼리가 여러 행을 내보내면 무엇과 비교해야 할지 정해지지 않아 오류가 납니다. 이때는 IN이나 ANY, ALL처럼 다중행 연산자를 써야 합니다.

놓인 자리가 이름을 정합니다

서브쿼리는 SQL 문 안에 들어간 또 하나의 SELECT 문입니다. 종류를 외우기 어려운 이유는 분류 기준이 두 개나 있어서인데, 하나는 어디에 놓였는가이고 다른 하나는 몇 행을 내보내는가입니다.

먼저 위치로 나눠 보죠.

위치 이름 하는 일
WHERE, HAVING 절 중첩 서브쿼리 조건의 비교 대상을 만듭니다
FROM 절 인라인 뷰 임시 테이블처럼 쓰입니다
SELECT 절 스칼라 서브쿼리 컬럼 하나처럼 쓰입니다

인라인 뷰는 FROM 절에 놓인 서브쿼리입니다. 이름 그대로 그 자리에서만 존재하는 뷰죠. 집계한 결과를 다시 조건으로 걸러야 할 때, 정렬한 결과에 순번을 붙여야 할 때 요긴합니다. 인라인 뷰에는 반드시 별칭을 주는 편이 좋고, 제품에 따라 필수입니다.

스칼라 서브쿼리는 SELECT 절에 놓여 값 하나를 돌려줍니다. 그래서 반드시 한 행 한 컬럼이어야 합니다. 여러 행이 나오면 오류이고, 한 행도 나오지 않으면 NULL이 됩니다. 오류가 아니라 NULL이라는 점을 기억해 두세요. 조인으로 붙였을 때 행이 사라지는 것과 달리, 스칼라 서브쿼리는 값이 비는 형태로 남습니다.

단일행이냐 다중행이냐

서브쿼리가 내보내는 행 수에 따라 함께 쓸 수 있는 연산자가 달라집니다.

단일행 서브쿼리는 한 행을 돌려주고, 등호나 부등호 같은 단일행 비교 연산자와 씁니다. "평균 급여보다 많이 받는 사원"이 전형적인 예죠.

다중행 서브쿼리는 여러 행을 돌려주므로 IN, ANY, ALL, EXISTS 같은 다중행 연산자와 써야 합니다. ANY는 하나라도 만족하면 참이고, ALL은 전부 만족해야 참입니다. 그래서 어떤 값보다 큰 값 중 하나라도 있으면 참인 조건은 최솟값과 비교하는 것과 같고, 전부보다 커야 하는 조건은 최댓값과 비교하는 것과 같습니다. 이 등가 관계가 시험에 나옵니다.

다중컬럼 서브쿼리도 있습니다. 여러 컬럼을 한꺼번에 비교하며, 부서별 최고 급여를 받는 사원처럼 두 값의 조합으로 찾아야 하는 문제에 씁니다.

연관 서브쿼리와 EXISTS

여기가 이 강의 핵심입니다. 서브쿼리가 바깥 쿼리의 컬럼을 참조하면 연관 서브쿼리(correlated subquery)입니다. 참조하지 않으면 비연관 서브쿼리입니다.

차이는 실행 방식입니다. 비연관 서브쿼리는 바깥과 무관하니 한 번 실행해 결과를 두고 씁니다. 연관 서브쿼리는 바깥 행마다 값이 달라지므로 행마다 다시 평가됩니다. 그래서 연관 서브쿼리는 바깥 결과가 클 때 비용이 커질 수 있습니다.

EXISTS는 연관 서브쿼리와 짝을 이루는 연산자입니다. "조건에 맞는 행이 하나라도 있는가"만 묻습니다. 특징이 둘 있습니다. 첫째, 하나를 찾으면 즉시 멈춥니다. 몇 건인지는 관심이 없으니까요. 그래서 존재 여부만 필요한 경우 COUNT로 세는 것보다 유리합니다. 둘째, 서브쿼리의 SELECT 절에 무엇을 적든 상관없습니다. 값을 보지 않으니까요.

그리고 2강에서 예고한 이야기가 여기서 완성됩니다. NOT IN은 목록에 NULL이 하나라도 있으면 결과가 통째로 비지만, NOT EXISTS는 그렇지 않습니다. NOT EXISTS는 값을 비교하지 않고 행의 존재만 보기 때문입니다. 그래서 "주문이 없는 고객" 같은 부정 조건은 NOT EXISTS로 쓰는 편이 안전합니다.

조인으로 쓸까, 서브쿼리로 쓸까

같은 결과를 조인으로도 서브쿼리로도 쓸 수 있는 경우가 많습니다. 기준을 하나 잡아 두면 편합니다. 결과에 상대 테이블의 값이 필요하면 조인, 존재 여부나 비교 기준만 필요하면 서브쿼리입니다.

주문한 고객의 이름과 주문일자를 함께 보여 줘야 한다면 조인입니다. 주문 테이블의 값이 결과에 나와야 하니까요. 반면 "주문한 적이 있는 고객 목록"이라면 서브쿼리가 낫습니다. 주문 테이블의 값은 결과에 필요하지 않고, 조인으로 쓰면 한 고객이 주문 건수만큼 중복되어 나와 다시 중복을 없애야 합니다.

한 가지 더, 서브쿼리는 바깥에서 안쪽을 볼 수 없습니다. 서브쿼리 안에서 만든 별칭이나 컬럼은 바깥 쿼리에서 참조할 수 없습니다. 반대로 안쪽에서 바깥을 참조하는 것은 연관 서브쿼리로 가능하죠. 방향이 한쪽뿐이라는 이 비대칭을 기억해 두면 인라인 뷰를 언제 써야 하는지가 분명해집니다. 안쪽 결과를 바깥에서 컬럼으로 쓰고 싶다면 FROM 절로 내려야 합니다.

인출 문제

문제 1. 스칼라 서브쿼리의 결과가 한 행도 나오지 않았을 때의 동작은?
스칼라 서브쿼리는 값이 없으면 NULL을 돌려줍니다. 조인은 짝이 없으면 행이 사라지지만 스칼라 서브쿼리는 행을 남기고 값만 비운다는 점이 다릅니다.
문제 2. 연관 서브쿼리에 대한 설명으로 옳은 것은?
바깥의 값을 참조하기 때문에 바깥 행이 바뀌면 결과도 바뀌므로 행마다 다시 평가됩니다. 한 번만 실행되는 것은 바깥을 참조하지 않는 비연관 서브쿼리입니다.
문제 3. 서브쿼리 결과에 NULL이 포함될 수 있는 상황에서 부정 조건을 쓸 때 NOT EXISTS가 NOT IN보다 안전한 이유는?
NOT IN은 각 값과의 비교를 AND로 이어 붙이므로 NULL이 끼면 참이 될 수 없습니다. NOT EXISTS는 값을 보지 않고 조건에 맞는 행이 있는지만 확인하므로 NULL의 영향을 받지 않습니다.
문제 4. "주문한 적이 있는 고객 목록"을 뽑을 때 조인보다 서브쿼리가 유리한 이유는?
상대 테이블의 값이 결과에 필요하지 않은 존재 여부 문제라 서브쿼리가 맞습니다. 조인으로 쓰면 일대다 관계 때문에 고객이 여러 번 나와 중복 제거를 한 단계 더 해야 합니다.

더 풀기

출제기준의 세세항목을 따라 이 강의 범위에서 새로 낸 문제입니다. 모의고사도 여기에서 뽑습니다.

묶음 1

문제 1. 서브쿼리를 가장 잘 설명한 것은?
서브쿼리는 문장 안에 들어간 질의입니다. 놓인 자리에 따라 이름과 쓰임이 달라집니다.
문제 2. WHERE 절에 놓인 서브쿼리를 부르는 이름은?
조건절에 놓이면 중첩 서브쿼리입니다. FROM에 놓이면 인라인 뷰, SELECT에 놓이면 스칼라 서브쿼리입니다.
문제 3. FROM 절에 놓인 서브쿼리를 부르는 이름은?
FROM 자리에 오면 하나의 테이블처럼 취급되므로 인라인 뷰라고 부릅니다.
문제 4. SELECT 절에 놓인 서브쿼리의 제약으로 옳은 것은?
스칼라 서브쿼리는 값 하나로 쓰이므로 결과가 여러 행이면 오류가 납니다. 값이 없으면 NULL이 됩니다.
문제 5. 스칼라 서브쿼리의 결과가 한 행도 없을 때 그 자리의 값은?
결과가 없으면 NULL로 채워집니다. 여러 행이 나오면 오류가 나는 것과 대조됩니다.
문제 6. 단일행 서브쿼리에 쓸 수 있는 연산자로 알맞은 것은?
결과가 한 행이므로 값 하나와 비교하는 연산자를 씁니다. 여러 행이 나오면 오류가 납니다.
문제 7. 다중행 서브쿼리에 쓸 수 없는 연산자는?
여러 행과 하나의 값을 등호로 비교할 수 없습니다. 목록과 비교하는 연산자를 써야 합니다.
문제 8. 서브쿼리가 여러 행을 돌려주는데 등호로 비교하면 어떻게 되나요?
값 하나를 기대하는 자리에 여러 개가 오면 실행 시점에 오류가 납니다. 데이터가 늘어난 뒤에야 터지는 경우가 많아 위험합니다.
문제 9. ANY 연산자를 부등호와 함께 쓸 때의 뜻으로 옳은 것은?
어느 하나만 만족하면 되므로 최솟값과 비교하는 것과 같아집니다.
문제 10. ALL 연산자를 부등호와 함께 쓸 때의 뜻으로 옳은 것은?
전부를 만족해야 하므로 가장 큰 값을 넘어야 합니다. ANY와 정확히 반대입니다.

묶음 2

문제 1. 급여 목록이 백, 이백, 삼백일 때 급여가 그 목록의 어느 하나보다 크다는 조건을 만족하는 최소 급여는?
ANY는 하나만 넘으면 되므로 최솟값 백을 넘으면 참입니다.
문제 2. 같은 목록에서 급여가 모든 값보다 크다는 조건을 만족하려면 급여는?
ALL은 전부를 넘어야 하므로 최댓값 삼백을 넘어야 합니다.
문제 3. 연관 서브쿼리를 가장 잘 설명한 것은?
바깥 값을 받아 쓰므로 독립적으로 실행할 수 없고 행마다 평가됩니다.
문제 4. 비연관 서브쿼리의 특징으로 옳은 것은?
바깥을 참조하지 않으면 값이 고정되므로 한 번만 계산하면 됩니다.
문제 5. EXISTS 연산자가 하는 일로 가장 정확한 것은?
EXISTS는 존재 여부만 봅니다. 그래서 한 건을 찾는 순간 더 보지 않고 멈출 수 있습니다.
문제 6. EXISTS를 쓸 때 서브쿼리의 SELECT 절에 무엇을 적어도 결과가 같은 이유는?
흔히 상수 일을 적는 것도 그래서입니다. 무엇을 적든 존재 판정에는 영향이 없습니다.
문제 7. NOT IN 대신 NOT EXISTS를 권하는 상황으로 가장 알맞은 것은?
NOT IN은 목록에 NULL이 하나만 있어도 결과가 비어 버립니다. NOT EXISTS는 그 함정이 없습니다.
문제 8. 주문한 적이 없는 고객을 NOT IN으로 찾았더니 결과가 하나도 나오지 않았습니다. 가장 가능성이 높은 이유는?
NULL과의 같지 않음 비교가 알 수 없음이 되고 AND로 이어져 어떤 행도 참이 되지 못합니다.
문제 9. 인라인 뷰를 쓰는 대표적인 이유로 가장 알맞은 것은?
한 번에 표현할 수 없는 두 단계 처리를 인라인 뷰가 이어 줍니다. 부서별 평균을 구한 뒤 그 평균과 비교하는 질의가 대표적입니다.
문제 10. 인라인 뷰에 별칭을 붙여야 하는 이유로 가장 알맞은 것은?
바깥에서 그 결과의 컬럼을 참조하려면 이름이 있어야 합니다. 제품에 따라 별칭 없이도 동작하기도 하지만 붙이는 편이 안전합니다.

묶음 3

문제 1. 자기 부서의 평균 급여보다 많이 받는 사원을 찾을 때 필요한 서브쿼리의 성격은?
비교 기준이 사원마다 다르므로 바깥 행의 부서를 받아 평균을 구해야 합니다. 인라인 뷰로 부서별 평균을 만들어 조인하는 방법도 있습니다.
문제 2. 전체 평균 급여보다 많이 받는 사원을 찾을 때 서브쿼리의 성격은?
기준값이 하나로 고정되므로 한 번만 계산하면 됩니다.
문제 3. 서브쿼리 안에서 바깥 질의의 컬럼을 참조할 수 있는 방향으로 옳은 것은?
안쪽은 바깥의 범위 안에 있으므로 바깥 이름을 볼 수 있습니다. 반대로 바깥에서 안쪽의 이름은 보이지 않습니다.
문제 4. 인라인 뷰의 컬럼을 바깥에서 쓰려면 어떻게 해야 하나요?
인라인 뷰는 하나의 테이블이므로 그 결과에 포함된 컬럼만 바깥에서 보입니다.
문제 5. 조인 대신 스칼라 서브쿼리를 써서 참조값을 가져올 때의 장점으로 가장 알맞은 것은?
값 하나만 필요할 때 유용합니다. 다만 행마다 실행되므로 대상 행이 많으면 비용이 커질 수 있습니다.
문제 6. 스칼라 서브쿼리를 쓸 때 주의할 점으로 가장 알맞은 것은?
지금은 한 행이어도 데이터가 늘면 여러 행이 될 수 있습니다. 그때 오류가 나므로 유일성을 미리 확인해야 합니다.
문제 7. 서브쿼리를 조인으로 바꾸었더니 결과 행 수가 늘어났습니다. 가장 가능성이 높은 이유는?
EXISTS는 몇 건이 있든 한 번만 참입니다. 조인으로 바꾸면 건수만큼 행이 생기므로 중복 제거가 필요해집니다.
문제 8. 다중 컬럼 서브쿼리를 가장 잘 설명한 것은?
부서별 최대 급여를 받는 사원처럼 두 값의 조합으로 비교해야 할 때 씁니다. 컬럼을 따로 비교하면 엉뚱한 조합이 걸립니다.
문제 9. 부서별 최대 급여를 받는 사원을 찾을 때 부서 조건과 급여 조건을 따로 쓰면 생기는 문제는?
급여 목록만 비교하면 어느 부서의 최댓값인지가 사라집니다. 부서와 급여를 한 쌍으로 비교해야 합니다.
문제 10. 다음 중 서브쿼리에 대한 설명으로 옳지 않은 것은?
조건 판단에는 순서가 필요 없습니다. 오히려 불필요한 정렬은 비용만 늘립니다.

이전: 7강 표준 조인 · 다음: 9강 집합 연산자

모던지 · 궁금하면 모던지 GitHub · 2026-09-10