쿼리문을 작성할 때, 그 안에 포함되는 다른 쿼리문. 실행되는 쿼리에 중첩으로 위치해, 정보를 전달한다.
SELECT [컬럼]
FROM [테이블]
WHERE [컬럼] IN (
SELECT [컬럼]
FROM [테이블]
WHERE [조건]
);
서브쿼리를 포함하고 있는 쿼리를 외부쿼리(outer query)라고 부르며, 서브쿼리(inner query)라고도 부른다.
서브쿼리는 위의 예시처럼 소괄호()로 묶어서 표현한다.
서브쿼리는 메인쿼리의 컬럼을 사용할 수 있지만, 메인 쿼리는 서브 쿼리의 컬럼을 사용할 수 없다.
MySQL에서 서브쿼리를 포함할 수 있는 외부쿼리는 SELECT, INSERT, UPDATE, DELETE, SET, DO 문이 있다.
이러한 서브쿼리는 또 다시 다른 서브 쿼리 안에 포함될 수 있다.
서브쿼리와 함께 활용 가능한 쿼리는 위에 명시된 쿼리문 말고도 IN, NOT IN, EXISTS, ANY, ALL 가 있다.
IN은 특정한 값이 서브쿼리에 있는지 확인할 수 있다. 다음 쿼리는 customers 테이블에서 CustomerId 의 값이 서브쿼리에서 돌려받는 값에 속한 결과들만 조회하고 있다.
SELECT *
FROM customers
WHERE CustomerId IN (
SELECT CustomerId
FROM customers
WHERE CustomerId < 10
);
만약 IN 대신 NOT IN 을 사용한다면, 서브쿼리에서 조회된 10 미만을 제외한(10을 초과하는) 레코드를 조회한다.
EXISTS 또는 NOT EXISTS는 돌려받은 서브쿼리에 존재하는 레코드를 확인한다. 만약 조회하려는 레코드가 존재한다면 참(TRUE)을, 그렇지 않은 경우에는 거짓(FALSE)을 리턴한다.
SELECT EmployeeId
FROM employees e
WHERE EXISTS (
SELECT 1
FROM customers c
WHERE c.SupportRepId = e.EmployeeId
)
ORDER BY EmployeeId
다음 쿼리문은 employees 테이블에서부터 'EmployeeId' 필드를 조회한다. 이때 서브쿼리로 customers 테이블의 'SupportRepId' 필드값과 employees 테이블의 'EmployeeId' 필드값을 비교해 일치하는 레코드들을 가져온다.
여러개의 비교값 중 하나라도 만족하면 true를 반환한다. IN과 다른점은 비교 연산자를 사용한다는 점이다.(자바의 OR)
# 서브 쿼리의 AGE 중 최소값보다 큰 AGE를 가진 데이터 조회
SELECT *
FROM TEST
WHERE AGE > ANY(SELECT AGE FROM TEST WHERE ADDR='서울');
전체 값을 비교해서 모두 만족해야 True를 반환한다 (자바의 AND)
# 서브 쿼리의 AGE 중 최대값보다 큰 AGE를 가진 데이터 조회
SELECT *
FROM TEST
WHERE AGE > ALL(SELECT AGE FROM TEST WHERE ADDR='서울');
SELECT [컬럼], (SELECT ...) -- 스칼라 서브쿼리(Scalar Sub Query): SELECT 문에 나타나는 서브쿼리, 컬럼처럼 사용
FROM (SELECT ...) -- 인라인 뷰(Inline View): FROM 문에 나타나는 서브쿼리, 테이블처럼 사용
WHERE [컬럼] = (SELECT ...) -- 중첩 서브 쿼리(Nested Sub Query): WHERE문에 나타나는 서브쿼리, 변수처럼 사용
-- 정대리 급여와 테이블 전체 평균 급여를 구하시오.
SELECT name, salary, (
SELECT ROUND(AVG(salary),-1)
FROM employee) AS '평균급여'
FROM employee
WHERE name = '정대리';
-- 직급이 사원인 사람들의 이름과 급여를 구하시오.
SELECT EX1.name,EX1.salary
FROM (
SELECT *
FROM employee AS Ii
WHERE Ii.office_worker='사원') EX1;
-- 테이블2의 정보를 뽑아서 그 데이터를 테이블1에 넣어준다.
-- value()들어갈 자리를 서브쿼리로 대체 했다.
INSERT INTO table1 (SELECT * FROM table2);
-- 인턴의 정보를 구해와서 삭제한다.
DELETE FROM employee
WHERE id = (SELECT id FROM employee where office_worker = '인턴' );
-- 인턴에 정보를 구해와서 급여를 10만원 인상한다.
UPDATE employee SET salary=(salary+100000)
WHERE id = (SELECT id FROM employee where office_worker = '인턴' );
서브쿼리의 쓰임새를 보면 JOIN에 대체하여 사용할 수 있을 것 같다. 하지만 이는 권장하지 않는 방법이다. 필요에 따라 둘 중 하나만이 유일한 해결방법일 수 있지만, JOIN을 사용할 수 있을 때, 서브쿼리 대신에 JOIN을 사용하도록 하자.
그 이유는 서브쿼리는 가독성이 좋지만 서능이 조인에 비해 매우 좋지 않다. 따라서 최신 MySQL은 사용자가 서브쿼리문을 사용하면 자체적으로 조인문으로 변환하여 실행시킨다고 한다.