서브쿼리(Subquery)
서브쿼리(subquery)란 다른 쿼리 내부에 포함되어 있는 SELECT 문을 의미한다.
서브쿼리를 포함하고 있는 쿼리를 외부쿼리(outer query)라고 부르며, 서브쿼리는 내부쿼리(inner query)라고도 부른다,
SELECT [컬럼]
FROM [테이블]
WHERE [컬럼] IN (
SELECT [컬럼]
FROM [테이블]
WHERE [조건]
);
서브쿼리는 위의 예시처럼 소괄호()로 묶어서 표현한다.
✅ 서브쿼리 - 메인쿼리 컬럼 사용 가능
✅ 메인쿼리 - 서브쿼리 컬럼 사용 불가
조인(join)에 참여하는 모든 테이블이 대등한 관계에 있기 때문에 조인에 참여하는 모든 테이블의 컬럼을 어느 위치에서라도 자유롭게 사용할 수 있는 조인과 달리, 서브쿼리는 그렇지 않다.
조인은 집합간의 곱의 관계이다. 따라서, 1:1 관계 테이블이 조인하면 1(= 1*1) 레벨의 집합이 생성되고, 1:M 관계가 조인하면서 M(= 1*M) 레벨의 집합이 된다. 그러나, 서브쿼리는 서브쿼리 레벨과는 상관없이 항상 메인쿼리 레벨로 결과 집합이 생성된다.
따라서 조인쿼리와 서브쿼리 방식을 상황에 맞게 사용하는 것이 중요하다.
서브쿼리 장점
- 서브쿼리는 쿼리를 구조화시키므로, 쿼리의 각 부분을 명확히 구분할 수 있게 해 준다.
- 서브쿼리는 복잡한 JOIN이나 UNION과 같은 동작을 수행할 수 있는 또 다른 방법을 제공한다.
- 서브쿼리는 복잡한 JOIN이나 UNION 보다 좀 더 읽기 편해 가독성이 좋다.
서브쿼리가 사용 가능한 곳
- SELECT
- FROM
- WHERE
- HAVING
- ORDER BY
- INSERT 문의 VALUES
- UPDATE 문의 SET
서브쿼리와 함께 사용하는 연산자
IN, NOT IN
SELECT name
FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location = 'New York'
);
IN은 서브쿼리의 결과에 포함된 값 중 하나와 일치하는지 확인한다.
반대로 NOT IN은 서브쿼리의 결과에 포함되지 않은 값과 일치하는지 확인한다.
EXISTS
SELECT name
FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e.department_id
AND d.location = 'New York'
);
EXISTS는 서브쿼리의 결과가 하나 이상의 행을 반환하는지 여부를 확인한다.
ANY, ALL
SELECT name
FROM employees
WHERE salary > ANY (
SELECT salary
FROM employees
WHERE department_id = 10
);
ANY는 서브쿼리에서 반환된 값 중 하나와 비교하여 조건이 참인지 확인한다.
위 쿼리는 department_id 가 10인 부서에서의 어떤 직원의 급여보다 높은 급여를 받는 직원의 이름을 조회한다. 서브쿼리에서 반환된 급여 중 하나와 비교하여 조건이 참이면 결과에 포함된다.
ALL은 서브쿼리에서 반환된 모든 값과 비교하여 조건이 참인지 확인한다.
위 쿼리에서 ANY가 아닌 ALL을 작성해 준다면 department_id 가 10인 부서의 모든 직원의 급여보다 높은 급여를 받는 직원의 이름을 조회한다. 서브쿼리에서 반환된 모든 급여보다 높은 급여를 받는 직원만 결과에 포함된다.
위치에 따른 서브쿼리
SELECT col1, (SELECT ...) -- 스칼라 서브쿼리(Scalar Sub Query): 하나의 컬럼처럼 사용 (표현 용도)
FROM (SELECT ...) -- 인라인 뷰(Inline View): 하나의 테이블처럼 사용 (테이블 대체 용도)
WHERE col = (SELECT ...) -- 일반 서브쿼리: 하나의 변수(상수)처럼 사용 (서브쿼리의 결과에 따라 달라지는 조건절)
스칼라 서브쿼리(Scalar Subquery)
- SELECT 문에 나타나는 서브쿼리
- 딴 테이블에서 어떠한 값을 가져올 때 쓰임
- 하나의 레코드만 리턴이 가능하며, 두 개 이상의 레코드는 리턴할 수 없다.
- 일치하는 데이터가 없더라도 NULL값을 리턴할 수 있다.
-- 정대리 급여와 테이블 전체 평균 급여를 구하시오.
SELECT name, salary, (
SELECT ROUND(AVG(salary),-1)
FROM employee) AS '평균급여'
FROM employee
WHERE name = '정대리';
인라인 뷰(Inline View)
- FROM 문에 나타나는 서브쿼리
- 뷰(View)처럼 결과가 동적으로 생성된 테이블로 사용할 수 있다. (임시적인 뷰이기 때문에 DB 저장 안 됨)
- 인라인 뷰로 동적으로 생성된 테이블이어서 인라인 뷰의 컬럼은 자유롭게 참조가 가능하다.
- 참고로 서브쿼리가 FROM 절에 사용될 경우 무조건 alias(별칭)을 지정해 주어야 한다.
-- 직급이 사원인 사람들의 이름과 급여를 구하시오.
SELECT EX1.name,EX1.salary
FROM (
SELECT *
FROM employee AS Ii
WHERE Ii.office_worker='사원'
) EX1; -- 서브쿼리 별칭
중첩 서브 쿼리
- 단일행, 복수(다중)행, 다중 컬럼과 같이 종류가 3가지로 나눠져 있다.
- 단일 행 : 말 그대로 단일 레코드를 리턴
- 복수(다중)행 : IN, ANY, ALL, EXISTS 등의 연산자로 얻은 서브쿼리 결과를 여러 개의 행으로 리턴
- 다중 컬럼 : 서브쿼리의 실행 결과로 여러 컬럼을 반환. 서브쿼리와 메인쿼리에서 비교하고자 하는 컬럼 개수와 컬럼의 위치가 동일해야 함.
결론
JOIN과 비슷한 역할을 하는 subquery는 가독성도 더 좋아서 JOIN을 대체할 수 있을 것 같지만 많은 개발자들이 권장하지 않는 방법이라고 말한다. 그 이유는 성능이 JOIN에 비해 좋지 않기 때문이다. 조만간 Subquery와 JOIN의 성능 차이를 주제로 포스팅을 다룰 예정이다.