1.8Subquery
SQL문을 중첩시켜 보다 복잡한 조건이나 값을 계산할 수 있는 서브쿼리
TL;DR
서브쿼리(Subquery)란, 하나의 SQL 문 안에 또 다른 쿼리를 중첩해서 사용하는 것을 말한다. 복잡한 조건이나 값을 계산해서 한 줄로 표현하고 싶을 때 쓰인다.
예를 들어, 'Toys' department의 모든 제품들보다 더 비싼 제품들의 이름과 가격을 보여주는 쿼리를 조회하고 싶다고 하자. 이 문제는 다음과 같이 2단계로 해결할 수 있다.
sql-- 1단계: Toys 부서에서 가장 비싼 가격 찾기 SELECT MAX(price) FROM products WHERE department = 'Toys';
sql-- 2단계: 해당 가격보다 비싼 가격 찾기 SELECT name, price FROM products WHERE price > (앞선 쿼리 결과값);
이런 식으로 쿼리의 결과를 또 다른 쿼리 안에 넣는 방식이 바로 서브쿼리다.
sqlSELECT name, price FROM products WHERE price > ( SELECT MAX(price) FROM products WHERE department = 'Toys' );
- 값이 쿼리로 계산이 필요한 곳에 ( )를 넣고 안에 쿼리를 넣는다.
- 서브쿼리 안에는
;
를 쓰지 않는다.
서브쿼리의 핵심: "결과 모양" 이해하기
서브쿼리는 쓰일 수 있는 곳이 조회하고자 하는 데이터 모수, 조회 결과 값, 조회 조건 등 쿼리의 모든 곳으로 아주 다양하다. 그 자리에서 무슨 값을 기대하는지(single value인지, row set인지 등)에 따라 구성 방식이 달라진다.
sqlSELECT p1.name, -- Value로 쓰이는 서브쿼리 (SELECT COUNT(name) FROM products) -- 테이블(row set)로 쓰이는 서브쿼리 FROM (SELECT * FROM products) AS p1 JOIN (SELECT * FROM products) AS p2 ON p1.id = p2.id -- 조건에 쓰이는 서브쿼리 WHERE p1.id IN (SELECT id FROM products);
안에 들어가는 Inner Query가 많은 열의 많은 행을 가지는 결과를 가져오는지, 하나의 값을 가져올지 등을 이해해야 한다.
SELECT 절 안의 서브쿼리
단일 값(Single Value)을 가져오는 서브쿼리를 SELECT
절에 넣을 수 있다.
sqlSELECT name, price,( SELECT MAX(price) FROM products ) FROM products WHERE price > 867;
- 모든 행에 대해 MAX(price) 값을 함께 보여준다.
- 여러 행을 반환하는 서브쿼리는 넣을 수 없다.
FROM 절 안의 서브쿼리
다양한 구조의 데이터를 반환하는 서브쿼리가 모두 가능하다.
1. 테이블처럼 쓰이는 서브쿼리
(참고로, 아래 예시는 반드시 필요한 상황에서 쓴 서브쿼리가 아니라 예시일 뿐이다.)
sqlSELECT name, price_weight_ratio FROM ( SELECT name, price / weight AS price_weight_ratio FROM products ) AS p WHERE price_weight_ratio > 5;
- 반드시 반환된 데이터를 alias로 재명명한
AS
구문을 포함해야 한다. SELECT
에서 사용할 수 있는 컬럼은 안쪽 쿼리에서 정의된 컬럼만이다.- 위의 예시에선 name, price_weight_ratio가 서브쿼리에서 정의되었기 때문에 밖에선 이 두 컬럼만 조회할 수 있다.
2. 단일 값만 반환하는 경우
sqlSELECT * FROM ( SELECT MAX(price) FROM products ) AS p;
JOIN 절 안의 서브쿼리
sqlSELECT first_name FROM users JOIN ( SELECT user_id FROM orders WHERE product_id = 3 ) AS o ON o.user_id = users.id;
- 역시
AS
로 별칭을 반드시 붙여야 한다. - 동적으로
JOIN
조건을 줄 때 유용하다.
WHERE 절 안의 서브쿼리
앞선 용례들보다 훨씬 자주 쓰이는 용례다.
sqlSELECT id FROM orders WHERE product_id IN ( SELECT id FROM products WHERE price / weight > 5; );
- 서브쿼리의 반환 형태에 따라 사용 가능한 연산자가 달라진다:
조건 형태 | 서브쿼리 결과 |
---|---|
WHERE product_id > (subquery) | 단일 값(single value) |
WHERE product_id IN (subquery) | 단일 컬럼의 여러 행 |
원하는 필터가 어떤 조건이냐에 따라 사용하는 연산자가 다를 것이고, 이에 따라 서브쿼리의 반환 형태도 다를 것이다.
새로운 WHERE 연산자: ALL, SOME, ANY
ALL
, SOME
등 집합 비교 연산자로 서브쿼리의 결과를 조건으로 활용할 수 있다.
- < ALL, ≥SOME, ≥ANY, <>ALL 등
sqlSELECT name, department, price FROM products WHERE price > ALL ( SELECT price FROM products WHERE department = 'Industrial' );
SOME(=ANY)
sqlSELECT name, department, price FROM products WHERE price > SOME ( SELECT price FROM products WHERE department = 'Industrial' );
SOME
은 사실상ANY
의 alias다.(상호대체 가능)
Correlated Subqueries (상관 서브쿼리)
Outer Query(바깥 쿼리)의 값을 Subquery(안쪽 쿼리)가 참조하는 경우.
예: 각 부서에서 가장 비싼 상품의 이름, 부서, 가격을 조회
sqlSELECT name, department, price FROM products AS p1 WHERE p1.price = ( SELECT MAX(price) FROM products AS p2 WHERE p1.department = p2.department )
FROM 없이 SELECT
FROM
을 쓰지 않고, subquery를 대신 쓰는 구문이다.
sqlSELECT ( SELECT MAX(price) FROM products );
- 위의 예시만 보면 쓸 일이 없어보이지만, 실제로는 비율 등 단일 값을 계산할 때 유용하다.
sqlSELECT ( SELECT MAX(price) FROM products ) / ( SELECT AVG(price) FROM products )
또, 여러 값들을 나열하는데 쓰임