서브쿼리를 어디에 놓았는지가 그 쿼리의 성격을 결정합니다. 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. "주문한 적이 있는 고객 목록"을 뽑을 때 조인보다 서브쿼리가 유리한 이유는?
상대 테이블의 값이 결과에 필요하지 않은 존재 여부 문제라 서브쿼리가 맞습니다. 조인으로 쓰면 일대다 관계 때문에 고객이 여러 번 나와 중복 제거를 한 단계 더 해야 합니다.
더 풀기
출제기준의 세세항목을 따라 이 강의 범위에서 새로 낸 문제입니다. 모의고사도 여기에서 뽑습니다.