SQL

[SQL] 아우터조인(OUTER-JOIN)

한기리 2021. 1. 29. 21:09
728x90
320x100

OUTER-JOIN

 

- OUTER-JOIN 이해하기

 

아우터-조인(OUTER-JOIN)은 조인 조건에 만족하지 않은 데이터도 결과에 나오도록 하는 조인이다.

 

 

아우터-조인에는 '기준 데이터 집합'과 '참조 데이터 집합'이 있다.

  • 기준 데이터 집합 : 아우터-조인의 기준이 되는 집합('아우터-집합' 이라고도 한다.)
  • 참조 데이터 집합 : 아우터-조인의 참조가 되는 집합

'기준 데이터 집합'은 조인 조건을 만족하지 않아도 모두 결과에 포함된다. 단, 필터 조건은 만족해야 한다.

 

아우터-조인을 사용하려면, 조인 조건 컬럼 한쪽에 '(+)' 표시를 추가하면 된다. 조인 조건에 '(+)'가 있으면 아우터-조인, 없으면 이너-조인이다.

 

 

 

'기준 데이터 집합'과 '참조 데이터 집합'은 '(+)' 표시로 구분한다. 조인 조건에 '(+)' 표시가 붙은 쪽은 '참조 데이터 집합', '(+)' 표시가 없는 쪽은 '기준 데이터 집합'이다.

 

 

예제 ) 이너-조인과 아우터-조인의 비교

 

-- CUS_ID가 'CUS_0002'의 조건을 갖는 M_CUS와 T_ITM_EVL 조인 

SELECT CUS_ID ,CUS_NM 
  FROM M_CUS 
 WHERE CUS_ID = 'CUS_0002'; 

SELECT CUS_ID ,ITM_ID ,EVL_LST_NO 
  FROM T_ITM_EVL 
 WHERE CUS_ID = 'CUS_0002';

고객(M_CUS)에서 'CUS_0002'의 데이터가 존재하지만 아이템평가(T_ITM_EVL)에는 'CUS_0002'의 데이터가 없다.

 

이를 가지고 이너-조인 및 아우터-조인을 하면 결과는 아래와 같이 나온다.

 

 

-- 이너-조인 

SELECT T1.CUS_ID ,T1.CUS_NM ,T2.CUS_ID ,T2.ITM_ID ,T2.EVL_LST_NO 
  FROM M_CUS T1 ,T_ITM_EVL T2 
 WHERE T1.CUS_ID = 'CUS_0002' 
 AND T1.CUS_ID = T2.CUS_ID;

 

-- 아우터-조인 

SELECT T1.CUS_ID ,T1.CUS_NM ,T2.CUS_ID ,T2.ITM_ID ,T2.EVL_LST_NO 
  FROM M_CUS T1 ,T_ITM_EVL T2 
 WHERE T1.CUS_ID = 'CUS_0002' 
 AND T1.CUS_ID = T2.CUS_ID(+);

이너-조인을 하면 아무 결과도 조회되지 않는다. 하지만 아우터-조인을 하면 'CUS_0002' 고객의 정보가 조회되는 것을 확인할 수 있다.

 

위 아우터-조인 조건은 'T1.CUS_ID = T2.CUS_ID(+)'이다.

  • 조인 조건에 '(+)' 표시가 있으니 아우터-조인이다.
  • '(+)' 표시가 붙은 T2(=T_ITM_EVL)는 아우터-조인의 '참조 데이터 집합'이다.
  • '(+)' 표시가 없는 T1(=M_CUS)은 아우터-조인의 '기준 데이터 집합'이다.(이때, 기준 데이터 집합의 참조쪽 결과는 NULL로 값이 채워진다.)
  • : 기준 데이터 집합은 조인에 성공 못 해도 결과값이 나온다.

예제 ) 아우터-조인, 한 명은 평가가 있음, 한 명은 평가가 없음

 

 

SELECT T1.CUS_ID, T1.CUS_NM ,T2.CUS_ID, T2.ITM_ID, T2.EVL_LST_NO 
  FROM M_CUS T1 ,T_ITM_EVL T2 
 WHERE T1.CUS_ID IN ('CUS_0002', 'CUS_0011') 
 AND T1.CUS_ID = T2.CUS_ID(+) ORDER BY T1.CUS_ID;

조인 조건을 보면 'T1.CUS_ID = T2.CUS_ID(+)' 이므로 M_CUS(T1) 가 '기준 데이터 집합'임을 알 수 있고, T_ITM_EVL(T2)이 '참조 데이터 집합'임을 알 수 있다. 따라서 '기준 데이터 집합'인 M_CUS(T1)에서는 조인 조건에 만족하지 않는 결과값도 모두 출력되는 것을 확인할 수 있다.

 

위 결과 값이 나오는 과정은 다음과 같다.

  • 1번은 M_CUS(T1)에서 두 건의 고객만 필터 된 데이터 집합이다.
  • 2번은 T_ITM_EVL(T2)의 데이터 집합이다.
  • 3번은 T1.CUS_ID = T2.CUS_ID(+) 조건으로 아우터-조인을 수행한 최종 결과다.: 조인 조건에 (+)가 붙은 2번은 참조 데이터 집합이다.: 참조 데이터 집합에 CUS_0011은 존재하므로 정상적으로 조인이 된다.
  • : 참조 데이터 집합에 CUS_0002는 존재하지 않음으로 NULL값으로 채워진다.
  • : 조인 조건에 (+)가 붙지 않은 1번은 기준 데이터 집합이다.

요약하자면

  • 기준 데이터 집합은 조인 조건에 (+)가 없는 쪽이다.
  • 참조 데이터 집합은 조인 조건에 (+)가 표시된 쪽이다.
  • 기준 데이터 집합은 조인 성공 여부와 상관없이 모두 나온다.(단, 필터 조건은 만족해야한다.)
  •  

- OUTER-JOIN의 필터 조건

 

 

아우터-조인에서는 '참조 데이터 집합'의 필터 조건에도 '(+)'표시를 추가해야한다.

 

예제) 필터 조건에 (+)표시 유무에 따른 결과 비교

 

SELECT T1.CUS_ID, T1.CUS_NM ,T2.CUS_ID, T2.ITM_ID ,T2.EVL_LST_NO, T2.EVL_DT 
  FROM M_CUS T1 ,T_ITM_EVL T2 
 WHERE T1.CUS_ID IN ('CUS_0073') 
 AND T1.CUS_ID = T2.CUS_ID(+) 
 AND T2.EVL_DT >= TO_DATE('20170201', 'YYYYMMDD') 
 AND T2.EVL_DT < TO_DATE('20170301', 'YYYYMMDD');

'T1.CUS_ID = T2.CUS_ID(+)' 아우터-조인을 사용했음에도 불구하고, 아무것도 조회되지 않은 것을 확인할 수 있다.이번에는 필터 조건'T2.EVL_DT >= TO_DATE('20170201', 'YYYYMMDD')' 와 'T2.EVL_DT < TO_DATE('20170301', 'YYYYMMDD')'쪽에 '(+)' 표시를 추가해보도록 한다.

 

SELECT T1.CUS_ID, T1.CUS_NM ,T2.CUS_ID, T2.ITM_ID ,T2.EVL_LST_NO, T2.EVL_DT 
  FROM M_CUS T1 ,T_ITM_EVL T2 
 WHERE T1.CUS_ID IN ('CUS_0073') 
 AND T1.CUS_ID = T2.CUS_ID(+) 
 AND T2.EVL_DT(+) >= TO_DATE('20170201', 'YYYYMMDD') 
 AND T2.EVL_DT(+) < TO_DATE('20170301', 'YYYYMMDD');

필터 조건 T2.EVL_DT에 (+) 표시를 추가하니 1 건 조회되었다.

이는 참조 데이터 집합 쪽 필터 조건에 '(+)' 표시 때문에 결과값이 다르게 출력된다.

  • 참조 쪽 필터 조건에 (+) 사용 : 아우터-조인 전에 필터 조건이 사용된다.
  • 참조 쪽 필터 조건에 (+) 미사용 : 아우터-조인 후, 조인 결과에 필터 조건이 사용된다.

위를 참고하면, 처음 쿼리는 참조 데이터 집합 쪽 필터 조건에 (+) 미사용한 경우에 해당하므로 이우터-조인 후, 조인 결과에 필터 조건이 사용되기때문에 아무것도 출력되지 않는다. 이는 결과적으로 이너-조인과 같다.

 

참조 데이터 집합 쪽 필터 조건에 (+) 한 경우는 아우터-조인 전에 필터 조건이 사용되기 때문에 각 데이터 집합에서 필터 조건이 된 후 아우터-조인이 된다.

- 실행이 불가능한 OUTER-JOIN

 

아우터-조인에서 '(+)'표시가 된 참조 데이터 집합은 두 개 이상의 기준 데이터 집합을 동시에 가질 수 없다. (11g 기준으로는 불가능하지만 상위 버전에서는 가능할 수도 있다.)

 

예제) 불가능한 아우터-조인

 

SELECT T1.CUS_ID, T2.ITM_ID, T1.ORD_DT, T3.ITM_ID, T3.EVL_PT 
  FROM T_ORD T1 ,T_ORD_DET T2 ,T_ITM_EVL T3 
 WHERE T1.ORD_SEQ = T2.ORD_SEQ 
 AND T1.CUS_ID = 'CUS_0002' 
 AND T1.ORD_DT >= TO_DATE('20170122', 'YYYYMMDD') 
 AND T1.ORD_DT < TO_DATE('20170123', 'YYYYMMDD') 
 AND T3.CUS_ID(+) = T1.CUS_ID 
 AND T3.ITM_ID(+) = T2.ITM_ID;

11g에서는 ORA-01417 : a table may be outer joined to at most one other table 과 같은 에러가 발생한다. 그러나 위와같이 18c에서는 참조 데이터 집합이 기준 데이터 집합을 두 개 이상가져도 정상적으로 결과가 출력되는 것을 확인할 수 있다.

 

11g기준으로 설명을 드리면, T3(아이템평가)가 T1(주문), T2(주문상세)와 동시에 아우터-조인을 하고 있다. 이와 같은 아우터-조인에서 참조 데이터 집합(T3)은 기준 데이터 집합을 동시에 두 개 이상 가질 수 없다.

 

이를 해결하려면 인라인-뷰를 사용해야한다. T1과 T2의 조인 결과를 인라인-뷰로 처리하면 인라인-뷰를 하나의 데이터 집합으로 받아들이므로 T3와 아우터-조인을 할 수 있다.

 

 

예제) 인라인-뷰를 사용한 아우터-조인

 

SELECT T0.CUS_ID, T0.ITM_ID, T0.ORD_DT ,T3.ITM_ID, T3.EVL_PT 
  FROM ( SELECT T1.CUS_ID, T2.ITM_ID, T1.ORD_DT 
           FROM T_ORD T1 ,T_ORD_DET T2 
          WHERE T1.ORD_SEQ = T2.ORD_SEQ 
          AND T1.CUS_ID = 'CUS_0002' 
          AND T1.ORD_DT >= TO_DATE('20170122', 'YYYYMMDD') 
          AND T1.ORD_DT < TO_DATE('20170123', 'YYYYMMDD') 
          ) T0 
          ,T_ITM_EVL T3 
 WHERE T3.CUS_ID(+) = T0.CUS_ID 
 AND T3.ITM_ID(+) = T0.ITM_ID 
ORDER BY T0.CUS_ID;

예제) ANSI 구문을 사용해 불가능한 아우터-조인 해결

 

SELECT T1.CUS_ID, T2.ITM_ID, T1.ORD_DT ,T3.ITM_ID, T3.EVL_PT 
  FROM T_ORD T1 
       INNER JOIN T_ORD_DET T2 
          ON (T1.ORD_SEQ = T2.ORD_SEQ 
              AND T1.CUS_ID = 'CUS_0002' 
              AND T1.ORD_DT >= TO_DATE('20170122', 'YYYYMMDD') 
              AND T1.ORD_DT < TO_DATE('20170123', 'YYYYMMDD')) 
       LEFT OUTER JOIN T_ITM_EVL T3 
          ON (T3.CUS_ID = T1.CUS_ID 
              AND T3.ITM_ID = T2.ITM_ID);

ANSI 구문을 사용하면 타 DBMS도 ANSI 표준을 지원하기 때문에 이관을 고려하면 괜찮은 방법이지만, 오라클에서는 성능을 위한 힌트가 잘 안 먹히는 경우가 있다고 한다.

 

- OUTER-JOIN이 포함된 여러 테이블의 조인

 

하나의 SQL에서 아우터-조인과 이너-조인을 동시에 사용할 때 주의할 점이 있다.

 

예제) 아우터-조인과 이너-조인을 동시에 사용하는 SQL

 

SELECT T1.CUS_ID, T2.ORD_SEQ, T2.ORD_DT, T3.ORD_SEQ, T3.ITM_ID 
  FROM M_CUS T1 ,T_ORD T2 ,T_ORD_DET T3 
 WHERE T1.CUS_ID = 'CUS_0073' 
 AND T1.CUS_ID = T2.CUS_ID(+) 
 AND T2.ORD_DT(+) >= TO_DATE('20170122', 'YYYYMMDD') 
 AND T2.ORD_DT(+) < TO_DATE('20170123', 'YYYYMMDD') 
 AND T3.ORD_SEQ = T2.ORD_SEQ;

위 쿼리는 아우터-조인을 사용하고 있음에도 불구하고 조회되는 데이터가 한 건도 없다.

 

그 이유는 아우터-조인의 결과에 이너-조인( T3.ORD_SEQ = T2.ORD_SEQ)을 수행하는데 아우터-조인의 결과값 중 T2.ORD_SEQ가 NULL값이 나오기 때문에 아우터-조인을 했음에도 데이터가 한 건도 없게 된 것이다.

조인 결과가 나오게 하려면 'T3.ORD_SEQ(+) = T2.ORD_SEQ'와 같이 아우터-조인으로 변경해야 한다.

위와 같이 여러 테이블이 조인될 때, 아우터-조인이 수행된 참조 데이터 집합은 기준 집합이 되어서 다른 테이블과 아우터-조인을 해야한다.

 

- OUTER-JOIN의 응용

아우터-조인은 조인에 성공하지 못해도 기준 데이터 집합은 무조건 조회되는 특징이 있다. 이와 같은 특징은 분석 리포트에서 실적이 없는 마스터도 결과에 포함시킬 때 유용하다.

 

예제) 고객ID별 주문건수, 주문이 없는 고객도 나오도록 처리

SELECT T1.CUS_ID ,COUNT(*) ORD_CNT_1 ,COUNT(T2.ORD_SEQ) ORD_CNT_2 
  FROM M_CUS T1 ,T_ORD T2 
 WHERE T1.CUS_ID = T2.CUS_ID(+) 
 AND T2.ORD_DT(+) >= TO_DATE('20170101', 'YYYYMMDD') 
 AND T2.ORD_DT(+) < TO_DATE('20170201', 'YYYYMMDD') 
GROUP BY T1.CUS_ID 
ORDER BY COUNT(*), T1.CUS_ID;

고객별 1월의 주문 건수를 구할 때 주문이 없는 고객은 주문건수가 0으로 조회되어야한다. 다음과 같이 아우터-조인을 사용하면 주문이 없는 고객도 모두 조회가 가능하다.

 

여기서 주문 건수를 구하기 위해 COUNT 집계함수를 사용하였는데, 1월에 주문이 하나도 없는 고객을 구하기 위해서는 NULL을 포함하여 COUNT하는 COUNT(*)이 아닌 COUNT(T2.ORD_SEQ)를 사용해야한다.

 

예제) 'PC, ELEC' 아이템 유형의 아이템별 주문수량 조회 (주문이 없어도 0으로 나와야 한다.)

 

SELECT T1.ITM_ID, T1.ITM_NM, NVL(T2.ORD_QTY, 0) 
  FROM M_ITM T1 
       ,( SELECT B.ITM_ID, SUM(B.ORD_QTY) ORD_QTY 
            FROM T_ORD A ,T_ORD_DET B 
           WHERE A.ORD_SEQ = B.ORD_SEQ 
           AND A.ORD_ST = 'COMP' 
           AND A.ORD_DT >= TO_DATE('20170101', 'YYYYMMDD') 
           AND A.ORD_DT < TO_DATE('20170201', 'YYYYMMDD') 
          GROUP BY B.ITM_ID ) T2 
 WHERE T1.ITM_ID = T2.ITM_ID(+) 
 AND T1.ITM_TP IN ('PC', 'ELEC') 
ORDER BY T1.ITM_TP, T1.ITM_ID;

인라인-뷰를 사용해 실적 데이터를 M_ITM 테이블의 PK 단위인 ITM_ID로 'GROUP BY' 한 후에 조인하고 있다.

아우터-조인의 특징을 정리

  • 아우터-조인은 기준 데이터 집합과 참조 제이터 집합으로서 조인이 이루어진다.
  • 참조 데이터 집합은 조인 조건에 (+)가 표시된 쪽이며 반대쪽은 기준 데이터 집합이 된다.
  • 기준 데이터 집합은 조인 조건을 만족하지 않아도 필터 조건만 만족하면 결과가 나온다.
  • (이 때, 참조 데이터 집합의 결과는 NULL 값으로 채워진다.)
  • 참조 데이터 집합의 필터 조건에 (+)를 표시하면 아우터-조인 전에 필터가 된다.
  • 참조 데이터 집합의 필터 조건에 (+)를 표시하지 않으면 아우터-조인 후 필터가 된다.
  • 일반적으로 참조 데이터 집합의 필터 조건에는 (+)를 표시한다.
  • 참조 데이터 집합이 다른 집합과 조인될 때는 기준 집합으로서 아우터-조인해야 한다.
728x90
320x100

'SQL' 카테고리의 다른 글

[SQL]카테시안조인(CARTESIAN JOIN)  (0) 2021.02.07