서브쿼리는 하나의 SELECT 쿼리 안에 포함된 또다른 SELECT 쿼리이다.

특정 컬럼의 평균값을 구해서 그 평균값보다 큰 데이터만 가져오는 것 처럼 쿼리 한번으로 힘든 경우 사용할 수 있다.

서브쿼리가 포함된 쿼리에서 가장 바깥의 쿼리는 메인쿼리라고 한다.

서브쿼리가 포함된 쿼리에서는 서브쿼리를 먼저 실행 후 메인쿼리를 실행하게 된다.

물론 쓰는 곳에 따라 다르며, SELECT 절이면 SELECT 직전에, WHERE 이면 WHERE 직전에 실행된다.

여기서도 논리적으로만 따졌을때이며, 물리적인 실행 순서는 옵티마이저의 결정에 따라 다를 수 있다.

-- 서브쿼리 없이 해결할 때
SELECT u.address
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.order_id = 1;
-- 이 쿼리의 실행결과가 '서울' 일때
SELECT name, address
FROM users
WHERE address = '서울';
-- 이런식으로 두번 나눠서 쿼리를 하게 되면 첫번째 쿼리의 결과가 나와야 다음 쿼리가 가능해서 번거롭고,
-- 두 쿼리가 트랜잭션 없이 단독으로 실행된다면 두번째 쿼리가 부정확할수도 있다.

-- 서브쿼리로 해결할 때
SELECT name, address
FROM users
WHERE address = (SELECT u.address
								 FROM users u
								 JOIN orders o ON u.user_id = o.user_id
								 WHERE o.order_id = 1);
-- 이렇게 두개의 쿼리를 하나로 합쳐 애플리케이션과 DB 간의 통신 횟수를 줄여 성능상 이점을 얻을 수 있다.
-- 이런식으로 '=', '>', '<=' 같은 단일 값 비교 연산자들과 같이 사용하는 서브쿼리가 스칼라 서브쿼리이다.
-- 카테고리가 DAILY 인 소설을 주문한 주문 데이터를 조회한다면 아래와 같이 하나로 조회할 수 있다.
SELECT o.order_id
FROM orders o
WHERE o.novel_id IN (SELECT novel_id FROM novels WHERE category = 'DAILY');
-- 만약 서브쿼리의 결과가 1,2,3 이라면 IN (1, 2, 3) 이 되고,
-- orders 중 novel_id가 1, 2, 3 중 하나인 데이터들은 전부 조회되게 된다.
-- IN 연산자도 NOT IN (1, 2, 3) 으로 쓰면 1, 2, 3이 아닌 데이터를 조회할 수 있다.

-- 만약 카테고리가 DAILY 인 소설들의 가격 중 가장 싼 가격보다 비싼 소설들을 가져온다면 아래처럼 쓸 수 있다.
SELECT title, author_id
FROM novels
WHERE price > ANY (SELECT price FROM novels WHERE category = 'DAILY');
-- 서브쿼리가 먼저 실행되면서 DAILY 카테고리인 소설의 가격들을 전부 조회한다.
-- 그 후 ANY 조건에 따라 price 가 서브쿼리의 결과값들 중 최솟값보다 큰 데이터들만 조회된다.

-- 만약 반대로 DAILY인 소설들의 가격 중 가장 비싼 가격보다 더 비싼 소설들을 가져온다면 아래처럼 쓸 수 있다.
SELECT title, author_id
FROM novels
WHERE price > ALL (SELECT price FROM novels WHERE category = 'DAILY');
-- ALL 뺴고는 전부 같으며, ALL은 모든 값보다 커야 하므로 최댓값보다 큰 데이터만 가져올 수 있다.

-- 사실 이런 ANY, ALL은 MIN, MAX와 대부분 대체가 가능하다.
-- 그리고 MIN, MAX가 더 직관적이고 이해하기 편하다고 생각하는 경우가 많아 보통 ANY, ALL은 많이 사용하지 않는다.
SELECT title, author_id
FROM novels
WHERE price > (SELECT MIN(price) FROM novels WHERE category = 'DAILY'):
-- 이렇게 대체가 가능하다.

주의사항