다음은 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 |
-- 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절 컬럼을 추가해보자.