9-5-1. (1:M):M 관계 조인


아래 ERD를 살펴보자. Shop과 Ord는 1:M 관계다. 그리고 Ord와 OrdDet도 1:M이다. 이 세 테이블의 관계를 간략하게 (1:M):M 이라고 말할 수 있다. 정확히는 앞쪽의 1:M 조인 결과가 나머지 M과 조인시에 1 집합이 된다. 복잡하니 간단하게 (1:M):M 조인라고 하도록 하자.

Untitled

Untitled

위와 같이 1:M:M 조인은 M:1:M 을 조인할 때 처럼 주의가 필요하지는 않다. 1부터 시작해서 조인 컬럼만 잘 생각해서 조인 처리하면 된다. 하지만 조인 후 GROUP BY 할 때는 주의가 필요하다.

아래와 같은 결과를 얻으려고 한다. 매장시작일이 2018년 3월인 매장의 2022년 1월의 주문건수와 주문수량 합을 구하려고 한다. 여기서 주문 건수는 주문 테이블의 데이터를 카운트해야 한다. 그리고 주문수량의 합은 주문상세의 주문수량(OrdQty)을 SUM 처리해야 한다.

-- (1:M):M을 조인해 원하는 결과
ShopOperTp  SUM_OrdCnt  SUM_OrdQty  
----------  ----------  ----------  
DIST        20          27          
DRCT        6           9           
FLAG        135         182          

위 결과를 보면 주문건수와 주문수량이 다르다는 점에 주의가 필요하다. 하나의 주문에 여러 상품을 여러 개 주문할 수 있기 때문에 주문수량은 주문 건수보다 더 클 수 있다.

위와 같은 결과를 얻기 위해 우선 Shop과 Ord, OrdDet를 조인해보자. 다음과 같다.

-- [SQL-9-5-1-a]
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopOperTp
        ,T2.OrdNo ,T2.OrdDtm
        ,T3.OrdDetNo ,T3.ItemId ,T3.OrdQty
FROM    startdb.Shop T1
        INNER JOIN startdb.Ord T2
            ON (T2.ShopId = T1.ShopId)
        INNER JOIN startdb.OrdDet T3
            ON (T3.OrdNo = T2.OrdNo)
WHERE  T1.ShopStartYmd LIKE '201803%'
AND    T2.OrdDtm >= STR_TO_DATE('20220101','%Y%m%d')
AND    T2.OrdDtm <  STR_TO_DATE('20220201','%Y%m%d')
ORDER BY T1.ShopId ,T2.OrdNo ,T3.OrdDetNo;

     ShopId  ShopNm         ShopOperTp  OrdNo  OrdDtm               OrdDetNo  ItemId  OrdQty  
     ------  -------------  ----------  -----  -------------------  --------  ------  ------  
     S008    San Diego-1st  FLAG        25286  2022-01-02 09:30:00  1         BGLR    1       
     S008    San Diego-1st  FLAG        25445  2022-01-05 11:30:00  1         HCHB    1       
     S008    San Diego-1st  FLAG        25456  2022-01-05 11:30:00  1         CLB     1       
     S008    San Diego-1st  FLAG        25504  2022-01-05 12:30:00  1         IAMR    1       
     S008    San Diego-1st  FLAG        25612  2022-01-11 11:30:00  1         CMFR    1       
-- > S008    San Diego-1st  FLAG        25623  2022-01-11 11:30:00  1         BMFR    1       
-- > S008    San Diego-1st  FLAG        25623  2022-01-11 11:30:00  2         ICLB    1       
     S008    San Diego-1st  FLAG        25671  2022-01-11 12:30:00  1         IAMB    1       
     S008    San Diego-1st  FLAG        25797  2022-01-23 11:30:00  1         IAMB    1     
... 생략 ... 

위 SQL의 조인은 정상이다. 다만, 위 SQL의 결과에 ‘-- >’로 표시한 부분처럼 하나의 주문에 여러 건의 주문 상세가 있을 수 있다는 점을 주의해야 한다.

이제 위 결과를 ShopOperTp별로 GROUP BY 처리해 주문건수와 주문수량 합계를 구해보자. 다음과 같이 SQL을 작성해볼 수 있다.

-- [SQL-9-5-1-b]
SELECT  T1.ShopOperTp
        ,COUNT(T2.OrdNo) SUM_OrdCnt
        ,SUM(T3.OrdQty) SUM_OrdQty
FROM    startdb.Shop T1
        INNER JOIN startdb.Ord T2
            ON (T2.ShopId = T1.ShopId)
        INNER JOIN startdb.OrdDet T3
            ON (T3.OrdNo = T2.OrdNo)
WHERE  T1.ShopStartYmd LIKE '201803%'
AND    T2.OrdDtm >= STR_TO_DATE('20220101','%Y%m%d')
AND    T2.OrdDtm <  STR_TO_DATE('20220201','%Y%m%d')
GROUP BY T1.ShopOperTp
ORDER BY T1.ShopOperTp;

ShopOperTp  SUM_OrdCnt  SUM_OrdQty  
----------  ----------  ----------  
DIST        27          27          
DRCT        9           9           
FLAG        182         182          

이제 SQL로 결과를 얻었으니, 보고서에 수치를 옮겨 적고 신나게 보고해보자. 진짜 이대로 보고 할 것인가? 잘 살펴보기 바란다. 위 수치는 문제가 있다. 주문건수와 주문수량 합계의 값이 같다. 조인 과정에서 Ord의 레코드가 OrdDet만큼 늘어났기 때문에, 주문건수를 단순 COUNT로 처리하면 이와 같은 문제가 발생된다.