9-8-1. LATERAL


MySQL은 8.0.35는 LATERAL 기능을 제공한다. DBMS와 버젼에 따라 LATERAL 기능은 제공하지 않을 수도 있다. 기본적으로 인라인 뷰에서는 외부 쿼리의 조건 절을 받아서 처리할 수 없다. 하지만 LATERAL을 적용하면 인라인 뷰에서 인라인 뷰 바깥쪽 쿼리의 조건 절을 받아 처리할 수 있다. 이너 조인과 아우터 조인 모두 LATERAL을 적용할 수 있다.

-- [SQL-9-8-1-a]
-- 일반 INNER JOIN, 인라인 뷰에서 ShopId별 GROUP BY후 1:1로 조인 처리
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        INNER JOIN (
            SELECT  A.ShopId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
            WHERE   A.OrdDtm >= STR_TO_DATE('20221001','%Y%m%d')
            AND     A.OrdDtm <  STR_TO_DATE('20221201','%Y%m%d')
            GROUP BY A.ShopID
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopStartYmd = '20180327';

-- [SQL-9-8-1-b]
-- 인라인 뷰에는 메인 쿼리의 startdb.Shop T1의 조건을 적어서 사용할 수 없다.
-- 아래와 같이 SQL을 실행하면 에러가 발생한다.
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        INNER JOIN (
            SELECT  A.ShopId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
            WHERE   A.OrdDtm >= STR_TO_DATE('20221001','%Y%m%d')
            AND     A.OrdDtm <  STR_TO_DATE('20221201','%Y%m%d')
            AND     A.ShopId = T1.ShopId -- > 에러 발생, 외부의 컬럼을 사용할 수 없다.
            GROUP BY A.ShopID
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopStartYmd = '20180327';

-- [SQL-9-8-1-c]
-- JOIN에 LATERAL 키워드를 추가하면 조건을 받아서 처리할 수 있다.
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        INNER JOIN LATERAL ( -- > LATERAL 키워드 추가
            SELECT  A.ShopId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
            WHERE   A.OrdDtm >= STR_TO_DATE('20221001','%Y%m%d')
            AND     A.OrdDtm <  STR_TO_DATE('20221201','%Y%m%d')
            AND     A.ShopId = T1.ShopId -- > LATERAL 키워드를 사용해 조건을 받을 수 있다.
            GROUP BY A.ShopID
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopStartYmd = '20180327';

마찬가지로 OUTER JOIN에도 LATERAL 을 사용할 수 있다.

-- [SQL-9-8-1-d]
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        LEFT OUTER JOIN LATERAL (
            SELECT  A.ShopId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
            WHERE   A.OrdDtm >= STR_TO_DATE('20221001','%Y%m%d')
            AND     A.OrdDtm <  STR_TO_DATE('20221201','%Y%m%d')
            AND     A.ShopId = T1.ShopId -- > LATERAL 키워드를 사용해 조건을 받을 수 있다.
            GROUP BY A.ShopID
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopId IN ('S070','S071');

LATERAL은 MySQL에서 성능 향상을 위해 사용하기도 한다. MySQL에서 인라인 뷰는 복잡도에 따라 메인 쿼리와 결합되지 못하고 내부적으로 완전히 별도 처리된다. 이로 인해 인라인 뷰에서 성능 저하가 발생될 수 있다. 아래 SQL들을 살펴보기 바란다.

-- [SQL-9-8-1-e]
-- 인라인 뷰에서 모든 매장의 주문 데이터를 SUM 처리
-- 필자 환경에서는 5.2초 정도 소요
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        INNER JOIN (
            SELECT  A.ShopId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
            GROUP BY A.ShopID
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopStartYmd = '20180327';

-- [SQL-9-8-1-f]
-- LATERAL을 사용해 인라인 뷰에서 외부 조건을 받아서 처리
-- 특정 매장의 주문에 대해서만 SUM 처리 된다.
-- 필자 환경에서 0.07초 소요
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        INNER JOIN LATERAL (
            SELECT  A.ShopId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
            WHERE   A.ShopId = T1.ShopID -- > LATERAL 키워들 사용, 외부 조건을 적용
            GROUP BY A.ShopID
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopStartYmd = '20180327';

LATERAL 키워드를 추가하자 저절로 속도가 빨라졌다. 참고로 필자의 환경은 MySQL 8.0.35 윈도우즈 버젼이다. 버젼이나 DBMS에 따라 속도가 향상되지 않을 수도 있다. 또한 SQL에 따라서는 LATERAL을 추가하면 오히려 느려질 수도 있다. 성능 향상을 원한다면 실행계획을 통해 문제점을 찾고 해결해야 한다. LATERAL을 사용하면 좋아진다고 외우는건 아무 도움이 되지 않는다. 나중에 성능 부분도 공부해 보기 바란다.

LATERAL은 인라인 뷰가 메인 쿼리에 영향을 받도록 작성할 수 있으며, 이로 인해 조건에 따라 인라인 뷰의 결과가 동적이라는 점이다. 아래 SQL은 Shop별로 주문 수량이 가장 많은 상품 Top-3만 조회하는 SQL이다. 아래의 패턴은 SQL을 오래전부터 해온 이들에게는 굉장히 생소한 패턴이다. 문제가 있을만한 패턴은 아니기 때문에 필요하다면 충분히 사용할만하다.

-- [SQL-9-8-1-g]
SELECT  T1.ShopId ,T1.ShopNm ,T1.ShopSize
        ,T2.ItemId ,T2.SUM_OrdAmt
FROM    startdb.Shop T1
        INNER JOIN LATERAL (
            SELECT  A.ShopId ,B.ItemId
                    ,SUM(A.OrdAmt) SUM_OrdAmt
            FROM    startdb.Ord A
                    INNER JOIN startdb.OrdDet B
                        ON (A.OrdNo = B.OrdNo)
            WHERE   A.OrdDtm >= STR_TO_DATE('20221001','%Y%m%d')
            AND     A.OrdDtm <  STR_TO_DATE('20221201','%Y%m%d')
            AND     A.ShopId = T1.ShopID -- > LATERAL 키워들 사용, 외부 조건을 적용
            GROUP BY A.ShopID ,B.ItemId
            LIMIT 3
            ) T2
            ON (T2.ShopID = T1.ShopId)
WHERE   T1.ShopStartYmd = '20180327';

ShopId  ShopNm         ShopSize  ItemId  SUM_OrdAmt  
------  -------------  --------  ------  ----------  
S014    Columbus-1st   53        AMB     43500.000   
S014    Columbus-1st   53        LEMR    36000.000   
S014    Columbus-1st   53        ICLB    32000.000   
S028    San Diego-2nd  81        ICLR    25500.000   
S028    San Diego-2nd  81        AMR     17000.000   
S028    San Diego-2nd  81        CMFR    12000.000   
S031    Austin-2nd     87        IAMB    32500.000   
S031    Austin-2nd     87        LEMR    50500.000   
S031    Austin-2nd     87        IAMR    35000.000