4.1 서브쿼리 (p.90)

4.1.1 서브쿼리 종류

다음은 SELECT절 단일값을 위배한 경우다.

-- 실행이 불가능한 SELECT 절의 서브쿼리
--SELECT 절의 서브쿼리에서 두 컬럼을 지정.
SELECT  T1.ORD_DT ,T1.CUS_ID
		,(SELECT A.CUS_NM ,A.CUS_GD FROM M_CUS A WHERE A.CUS_ID = T1.CUS_ID) CUS_NM_GD
FROM    T_ORD T1
WHERE   T1.ORD_DT >= TO_DATE('20170401','YYYYMMDD')
AND     T1.ORD_DT < TO_DATE('20170501','YYYYMMDD');

--SELECT 절의 서브쿼리에서 두 건 이상의 데이터가 나오는 경우.
SELECT  T1.ORD_DT ,T1.CUS_ID
		,(SELECT A.ITM_ID FROM T_ORD_DET A WHERE A.ORD_SEQ = T1.ORD_SEQ) ITM_LIST
FROM    T_ORD T1
WHERE   T1.ORD_DT >= TO_DATE('20170401','YYYYMMDD')
AND     T1.ORD_DT < TO_DATE('20170501','YYYYMMDD');

(해결책)

-- 고객 이름과 등급을 concat하여 하나의 컬럼으로 처리
-- 단가(UNT_PRC)와 주문수량(ORD_QTY)를 곱해서 주문금액으로 처리.
SELECT  T1.ORD_DT ,T1.CUS_ID
		,(SELECT A.CUS_NM||'('||CUS_GD||')' FROM M_CUS A WHERE A.CUS_ID = T1.CUS_ID) CUS_NM_GD
		,(SELECT SUM(A.UNT_PRC * A.ORD_QTY) FROM T_ORD_DET A WHERE A.ORD_SEQ = T1.ORD_SEQ) ORD_AMT
FROM    T_ORD T1
WHERE   T1.ORD_DT >= TO_DATE('20170401','YYYYMMDD')
AND     T1.ORD_DT < TO_DATE('20170501','YYYYMMDD');

| ORD_DT                  | CUS_ID   | CUS_NM_GD    | ORD_AMT |
| ----------------------- | -------- | ------------ | ------- |
| 2017-04-01 00:00:00.000 | CUS_0001 | NAME_0001(A) | 3,400   |
| 2017-04-01 00:00:00.000 | CUS_0011 | NAME_0011(B) | 3,500   |
| 2017-04-01 00:00:00.000 | CUS_0021 | NAME_0021(A) | 3,600   |
...
...
...
| 2017-04-30 00:00:00.000 | CUS_0090 | NAME_0090(B) | 1,100   |
| 2017-04-07 00:00:00.000 | CUS_0037 | NAME_0037(A) | 300     |
| 2017-04-08 00:00:00.000 | CUS_0018 | NAME_0018(A) | 1,000   |

(응용)

-- 고객별 마지막 ORD_SEQ의 주문금액
-- 서브쿼리가 MAX를 사용하기 때문에 T_ORD 테이블의 모든 레코드를 읽어야하는 단점이 있음
SELECT  T1.CUS_ID
		,T1.CUS_NM
		,(SELECT  TO_NUMBER(
									SUBSTR(
										MAX(LPAD(TO_CHAR(A.ORD_SEQ),8,'0') || TO_CHAR(A.ORD_AMT)	
									),9
							)
		 )
		FROM T_ORD A WHERE A.CUS_ID = T1.CUS_ID) LAST_ORD_AMT
FROM    M_CUS T1
ORDER BY T1.CUS_ID;

(응용 - 중첩서브쿼리 : WHERE절 서브쿼리)

-- 고객별 마지막 ORD_SEQ의 주문금액
-- SELECT절의 상관스칼라서브쿼리는 데이터건수만큼 반복 실행된다.
-- 따라서 조회건수가 적을 때만 아래처럼 스칼라서브쿼리 내에서 중첩서브쿼리를 이용하면 좋다.
SELECT  T1.CUS_ID
				,T1.CUS_NM
				,( -- 상관 스칼라서브쿼리
					SELECT  B.ORD_AMT
					FROM    T_ORD B 
					-- 중첩서브쿼리
					WHERE   B.ORD_SEQ = 
								(SELECT MAX(A.ORD_SEQ) FROM T_ORD A WHERE A.CUS_ID = T1.CUS_ID)
				 ) LAST_ORD_AMT
FROM    M_CUS T1
ORDER BY T1.CUS_ID;

(잘못된 쿼리 예)

-- 잠재적인 오류가 존재하는 서브쿼리 – 정상 실행
SELECT  T1.ORD_DT
		,T1.CUS_ID
		,(SELECT A.ORD_QTY FROM T_ORD_DET A WHERE A.ORD_SEQ = T1.ORD_SEQ) ORD_AMT
FROM    T_ORD T1
WHERE   T1.ORD_SEQ = 2297;

-- 잠재적인 오류가 존재하는 서브쿼리 – 오류 발생
--1. 오류가 발생하는 서브쿼리(ORD_SEQ = 2291)
SELECT  T1.ORD_DT
		,T1.CUS_ID
		,(SELECT A.ORD_QTY FROM T_ORD_DET A WHERE A.ORD_SEQ = T1.ORD_SEQ) ORD_AMT
FROM    T_ORD T1
WHERE   T1.ORD_SEQ = 2291;

--2. T_ORD_DET에 ORD_SEQ가 2291인 데이터는 두 건이 존재한다.
SELECT A.ORD_QTY FROM T_ORD_DET A WHERE A.ORD_SEQ = 2291

    ORD_QTY
-----------
|       1 |
|       1 |

4.1.2 SELECT절 단독서브쿼리

-- 17년8월 총 주문금액 구하기 – SELECT절 단독 서브쿼리
SELECT  TO_CHAR(T1.ORD_DT, 'YYYYMMDD') ORD_YMD
		,SUM(T1.ORD_AMT) ORD_AMT
		,(
			SELECT  SUM(A.ORD_AMT)
			FROM    T_ORD A 
			WHERE   A.ORD_DT >= TO_DATE('20170801','YYYYMMDD') 
			AND     A.ORD_DT < TO_DATE('20170901','YYYYMMDD')
		) TOTAL_ORD_AMT
FROM    T_ORD T1
WHERE   T1.ORD_DT >= TO_DATE('20170801','YYYYMMDD')
AND     T1.ORD_DT < TO_DATE('20170901','YYYYMMDD')
GROUP BY TO_CHAR(T1.ORD_DT, 'YYYYMMDD');

주문년월일  / 일자별주문수량 / 월주문수량총계
| ORD_YMD  | ORD_AMT | TOTAL_ORD_AMT |  
| -------- | ------- | ------------- |
| 20170806 | 31,000  | 505,700       |
| 20170803 | 13,800  | 505,700       |
| 20170814 | 13,200  | 505,700       |
| 20170816 | 32,500  | 505,700       |
| 20170825 | 12,600  | 505,700       |
| 20170831 | 22,600  | 505,700       |
| 20170802 | 9,000   | 505,700       |
| 20170804 | 18,900  | 505,700       |
...
...
...

| 20170813 | 8,400   | 505,700       |
| 20170820 | 22,600  | 505,700       |
| 20170829 | 11,000  | 505,700       |
| 20170805 | 36,000  | 505,700       |
| 20170811 | 2,800   | 505,700       |
| 20170812 | 3,300   | 505,700       |
| 20170817 | 33,500  | 505,700       |

위 쿼리에 주문금액비율 이라는 SELECT절 컬럼을 추가해보자.