서브쿼리 사용하기
목표
- 서브쿼리는 쿼리 안에 있는 쿼리이다. 여기서는 서브쿼리가 무엇이고 어떻게 사용하는지 학습한다.
목차
서브쿼리로 필터링하기
쿼리
란 모든 SQL 명령문
을 말하지만, 일반적으로 SELECT
문을 지칭할 때 사용한다.
여러 개의 테이블에 흩어져있는 정보를 바탕으로 원하는 결과를 얻기 위해서는 여러 개의 질의문을 던져 그 결과를 확인할 수 있다.
예를 들어 RGAN01
이라는 제품을 구매한 고객의 목록을 원한다고 가정해보자.
이정보를 가져오려면 다음 순서대로 진행해야 한다.
RGAN01
을 주문한 주문 번호를 가져온다.- 이전 단계에서 가져온 주문 번호로 고객 ID를 가져온다.
- 이전 단계에서 가져온 고객 ID로 고객의 상세 정보를 가져온다.
이 세 단계는 별도의 쿼리로 수행할 수 있지만, 번거롭다.
이 때 서브쿼리를 이용하면 세 개의 쿼리를 합쳐서 하나의 문장으로 만들 수 있다.
SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01';
SELECT cust_id
FROM Orders
WHERE order_num IN (20007, 20008);
# 서브쿼리를 사용한 결과와 동일하다.
SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01');
서브쿼리는 항상 안에 있는 쿼리를 먼저 처리하고, 그 다음 바깥쪽에 있는 쿼리를 처리한다.
위의 SELECT문
을 처리할 때, DBMS
는 실제로 두 번의 작업을 수행한다.
먼저 첫 번째 서브쿼리를 수행한다. SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01'
이 후 반환된 값을 바깥에 있는 쿼리의 WHERE절로 전달한다. 각각의 값은 IN 연산자를 사용할 때 처럼 콤마로 구분되어 전달된다.
[!NOTE] SQL 서식
서브쿼리를 갖는SELECT 문
은 읽거나 디버깅하기가 쉽지 않다.SELECT 문
이 복잡할 때는 더 그렇기 때문에 쿼리를 여러 개의 줄로 나눠서 적고 각 쿼리에 맞춰 절절히 들여쓰기하면,
서브쿼리를 작성한 코드도 한결 보기 편한다.
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN (1000000004, 1000000005);
# 서브쿼리를 사용한 결과와 동일하다.
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN (
SELECT cust_id
FROM Orders
WHERE order_num IN (
SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01'));
위 서브쿼리를 실행한 SQL은 DBMS
에 실제로 세 개의 SELECT문
을 수행한다.
가장 안쪽에 있는 서브쿼리에서는 주문 번호를 가져오고, 가져온 주문 번호는 그 바깥쪽에 있는 서브쿼리의 WHERE 절
에 사용된다.
두 번째 서브쿼리는 고객 ID를 가져오고, 고객 ID는 가장 바깥쪽에 있는 쿼리의 WHERE 절
에 사용된다.
가장 바깥쪽에 있는 쿼리가 실제 원하는 데이터를 가져오는 것이다.
WHERE 절
에 서브쿼리를 사용하면 매우 강력하고 유연한 SQL 문
을 작성할 수 있고, 사용할 수 있는 서브쿼리의 수에는 제한이 없지만, 너무 많은 서브쿼리를 사용하면 성능이 저하될 수 있다는 점에 주의하자
[!NOTE]
서브쿼리는 하나의 열만 검색할 수 있다. 여러 개의 열을 서브쿼리로 검색하면 에러가 발생한다.여기서 사용한 서브쿼리는 잘 작동하며, 원하는 데이터를 가져오지만 서브쿼리를 사용하는 것이 항상 효율적인 것은 아니다.
계산_필드로_서브쿼리_사용하기
서브쿼리를 사용하는 또 다른 방법은 계산 필드를 생성하는 것이다.
SELECT COUNT(*) AS orders
FROM Orders
WHERE cust_id = 1000000001;
SELECT cust_name,cust_state,(SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers ORDER BY cust_name;
이 예제에서는 서브쿼리가 다섯 번 수행된다. 그 이유는 다섯 명의 고객이 반환되었기 때문이다.
서브쿼리에 있는 WHERE 절
은 이전에 사용된 WHERE 절
과는 조금 다르다.
이 문장에서는 Orders.cust_id나 Customers.cust_id
와 같이 테이블과 열 이름을 모두 명시하는 완전한 열 이름 (Fully Qualified Column Name)을 사용하였다.
열 이름이 모호할 때마다 테이블 이름과 열 이름을 마침표(.)로 구분하여 적는 문법을 사용한다.
SELECT cust_name,cust_state,(SELECT COUNT(*)
FROM Orders
WHERE cust_id = cust_id) AS orders
FROM Customers
ORDER BY cust_name;
여기선 열 이름을 명시하지 않았다. 그 결과 우리가 원하는것과 다른 결과를 반환 받게된다.
[!NOTE] 완전한 열 이름 (Fully Qualified Column Name)
DBMS가 우리의 의도를 제대로 해석하지 못해 잘못된 결과를 가져올 수도 있고,
가끔은 열 이름이 중복되어 실제로 DBMS가 에러를 내는 경우도 있다.
예를 들어 WHERE 절이나 ORDER BY 절에서 지정한 열이 여러 테이블에 존재하는 경우 에러가 발생한다.
SELECT 문에서 두 개 이상의 테이블을 사용한다면 완전한 열 이름을 사용하는 것이 모호함을 피하는 좋은 방법이다.
[!caution]
서브쿼리가 항상 최선의 선택은 아니다.
이런 식의 데이터 검색보다 조인을 통한 데이터 검색이 더 효율적이다.
심화
SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE item_price >= 10);
SELECT cust_id, order_date
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'BR01')
ORDER BY order_date;
SELECT cust_email
FROM Customers
WHERE cust_id IN(SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'BR01'));
SELECT cust_id,(SELECT SUM(item_price * quantity)
FROM OrderItems
WHERE Orders.order_num = OrderItems.order_num) AS total_ordered
FROM Orders ORDER BY total_ordered DESC;
SELECT prod_name,(SELECT SUM(quantity)
FROM OrderItems
WHERE Products.prod_id = OrderItems.prod_id) AS quant_sold
FROM Products;