Database/SQL

서브쿼리 사용하기

onddd 2023. 9. 29. 17:19
728x90

목표

  • 서브쿼리는 쿼리 안에 있는 쿼리이다. 여기서는 서브쿼리가 무엇이고 어떻게 사용하는지 학습한다.

목차

  1. 서브쿼리로_필터링하기
  2. 계산_필드로_서브쿼리_사용하기
  3. 심화

서브쿼리로 필터링하기

쿼리란 모든 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;