이 영역을 누르면 첫 페이지로 이동
천천히 꾸준히 조용히 블로그의 첫 페이지로 이동

천천히 꾸준히 조용히

페이지 맨 위로 올라가기

천천히 꾸준히 조용히

천천히 꾸준히 조용히.. i3months 블로그

[MySQL] WITH RECURSIVE 계층 쿼리

  • 2024.12.13 22:51
  • 💡 솔루션
반응형

 

 

 

Oracle에서는 트리 형태의 계층 구조를 가진 데이터를 쿼리로 뽑아낼 때 CONNECT BY / START WITH 구문으로 쉽게 작성할 수 있는데.. MySQL에서는 전용 구문을 제공하지 않아 백엔드 레벨에서 데이터를 가공하거나 여러 집계 쿼리를 사용해 우회해서 쿼리를 작성해야 됐다.

 

MySQL 8.0 버전부터는 Common Table Expression을 지원하고, 이 구문을 활용해 CONNECT BY / START WITH 구문 없이 재귀적 계층 쿼리를 작성할 수 있다. 

 

WITH RECURSIVE 구문으로 임시테이블을 정의하고 해당 테이블을 스스로 다시 참조하는 방식이다. 

 

WITH RECURSIVE TEMP_TABLE AS (
    -- 시작점 (앵커 쿼리)
    SELECT ... 
    FROM ...
    WHERE ...  -- 시작점 조건

    UNION ALL

    -- 재귀 쿼리
    SELECT ... 
    FROM TABLE
    JOIN TEMP_TABLE ON TABLE.HIGH = TABLE.DOWN
)
SELECT * FROM TEMP_TABLE;

 

 

처음 실행될 때 앵커 쿼리가 실행돼 임시 테이블의 초기 상태를 정의한다.

첫 실행 결과를 바탕으로 재귀 쿼리가 실행되고 재귀 쿼리는 임시테이블 스스로를 참조해 앵커 쿼리 결과나 이전 재귀 쿼리 결과로 다음 계층을 조회한다.

더 이상 매칭되는 하위 컬럼이 없을 때 까지 반복한다. (종료 조건)

 

앵커 쿼리가 START WITH 구문에 해당하고, 재귀 쿼리의 연결 조건이 CONNECT BY와 PRIOR 구문에 해당한다고 생각하면 된다.

 

쿼리 결과로 임시테이블에는 각 단계마다 데이터가 누적되고, 그 결과로 오라클의 CONNECT BY 를 사용할 때와 같은 결과를 얻을 수 있다. 

 

SELECT 
	 C_INST_NO AS SRVC_VIEW_CD
   , LIST_ID
   , C_INST_NO AS SRVC_LIST_ID
   , UP_LIST_ID AS UP_SRVC_VIEW_CD
   , LIST_CM
   , SE_CD AS PUB_SE
   , LIST_NM
   , CONCAT(LIST_NM,(SELECT CD_NM FROM TC_CM WHERE CD_ID = '000007' AND CD = LST.SE_CD)) AS LIST_PUB_NM
   , LIST_NM AS LIST_ENG_NM
   , ROWNUM AS LIST_SN
   , TBL_ID
   , TBL_NM
   , PUB_CNT
   , LEVEL AS LV
   , EXP
   , OPEN
FROM (
	SELECT 
  		 C_INST_NO
	   , NULL AS UP_LIST_ID
	   , '0' AS LIST_ID
	   , (SELECT CD_NM FROM TC_CM WHERE CD_ID = '000008' AND CD =  C_INST_NO) AS LIST_CM
	   , (SELECT CD_NM FROM TC_CM WHERE CD_ID = '000008' AND CD =  C_INST_NO) AS LIST_NM
	   , NULL AS TBL_ID
	   , NULL AS TBL_NM
	   , '12345' AS SE_CD
	   , 0 AS PUB_CNT
	   , 0 AS EXP
	   , '' AS OPEN
	   , 0 AS UP_LIST_SN
	   , 0 AS LIST_SN
    FROM TB_C_LIST
    
   UNION ALL
   
	SELECT 
		A.C_INST_NO
	   , CASE 
			WHEN A.UP_LIST_ID IS NULL THEN '0' 
			ELSE A.UP_LIST_ID 
		END AS UP_LIST_ID
	   , A.LIST_ID
	   , '' AS LIST_CM
	   , A.LIST_NM
	   , A.TBL_NM
	   , A.TBL_ID
	   , A.SE_CD
	   , CASE WHEN A.TBL_ID IS NOT NULL
		      THEN 0
		      ELSE (SELECT COUNT(*)
                      FROM STMS.TB_ATH_LIST
                     WHERE LIST_ID = a.LIST_ID
                       AND TBL_ID IS NOT null
                      )
		 END AS PUB_CNT
	   , 0 AS EXP
	   , 'N' AS OPEN
	   , NVL(UP_LIST_SN, 0) AS UP_LIST_SN
	   , NVL(LIST_SN, 0) AS LIST_SN
    FROM TB_A_LIST A
   WHERE TBL_ID IS NULL
  ) LST
START WITH LST.UP_LIST_ID IS NULL
CONNECT BY PRIOR LST.LIST_ID = LST.UP_LIST_ID
ORDER SIBLINGS BY LST.UP_LIST_SN, LST.LIST_SN

 

Oracle 에서 작성된 계층 쿼리를 

 

WITH RECURSIVE LST AS (
    SELECT 
           C_INST_NO,
           NULL AS UP_LIST_ID,
           '0' AS LIST_ID,
           (SELECT CD_NM FROM TC_CMDTLCD WHERE CD_ID = '000008' AND CD = C_INST_NO) AS LIST_CM,
           (SELECT CD_NM FROM TC_CMDTLCD WHERE CD_ID = '000008' AND CD = C_INST_NO) AS LIST_NM,
           NULL AS TBL_ID,
           NULL AS TBL_NM,
           '12345' AS SE_CD,
           0 AS PUB_CNT,
           0 AS EXP,
           '' AS OPEN,
           0 AS UP_LIST_SN,
           1 AS LIST_SN
    FROM TB_C_LIST
    
    UNION ALL
    
    SELECT A.C_INST_NO,
           CASE WHEN A.UP_LIST_ID IS NULL THEN '0' ELSE A.UP_LIST_ID END AS UP_LIST_ID,
           A.LIST_ID,
           '' AS LIST_CM,
           A.LIST_NM,
           A.TBL_NM,
           A.TBL_ID,
           A.SE_CD,
           CASE 
             WHEN A.TBL_ID IS NOT NULL THEN 0
             ELSE (
               SELECT COUNT(*) 
                 FROM KRICT_DEV.TB_ATH_LIST 
                WHERE list_ID = A.LIST_ID 
                  AND TBL_ID IS NOT null
             )
           END AS PUB_CNT,
           0 AS EXP,
           'N' AS OPEN,
           IFNULL(A.UP_LIST_SN, 0) AS UP_LIST_SN,
           IFNULL(A.LIST_SN, 0) AS LIST_SN
      FROM TB_A_LIST A
     WHERE A.TBL_ID IS NULL
),

CTE AS (
    SELECT
        LST.C_INST_NO AS SRVC_VIEW_CD,
        LST.LIST_ID,
        LST.C_INST_NO AS SRVC_LIST_ID,
        LST.UP_LIST_ID AS UP_SRVC_VIEW_CD,
        LST.LIST_CM,
        LST.SE_CD AS PUB_SE,
        LST.LIST_NM,
        CONCAT(LST.LIST_NM,(SELECT CD_NM FROM TC_CMDTLCD WHERE CD_ID = '000007' AND CD = LST.SE_CD)) AS LIST_PUB_NM,
        LST.LIST_NM AS LIST_ENG_NM,
        LST.TBL_ID,
        LST.TBL_NM,
        LST.PUB_CNT,
        LST.EXP,
        LST.OPEN,
        LST.UP_LIST_SN,
        LST.LIST_SN,
        1 AS LV,
        CAST( CONCAT(LPAD(LST.UP_LIST_SN, 5, '0'), '-', LPAD(LST.LIST_SN, 5, '0')) AS CHAR(9999)) AS SORT_PATH
     FROM LST
     WHERE LST.UP_LIST_ID IS NULL

     UNION ALL

     SELECT
        L.C_INST_NO AS SRVC_VIEW_CD,
        L.LIST_ID,
        L.C_INST_NO AS SRVC_LIST_ID,
        L.UP_LIST_ID AS UP_SRVC_VIEW_CD,
        L.LIST_CM,
        L.SE_CD AS PUB_SE,
        L.LIST_NM,
        CONCAT(L.LIST_NM,(SELECT CD_NM FROM TC_CMDTLCD WHERE CD_ID = '000007' AND CD = L.SE_CD)) AS LIST_PUB_NM,
        L.LIST_NM AS LIST_ENG_NM,
        L.TBL_ID,
        L.TBL_NM,
        L.PUB_CNT,
        L.EXP,
        L.OPEN,
        L.UP_LIST_SN,
        L.LIST_SN,
        C.LV + 1 AS LV,        
        CAST( CONCAT(C.SORT_PATH, '-', LPAD(L.UP_LIST_SN, 5, '0'), '-', LPAD(L.LIST_SN, 5, '0')) AS CHAR(9999)) AS SORT_PATH
      FROM LST L
      JOIN CTE C ON L.UP_LIST_ID = C.LIST_ID
)

SELECT
	SRVC_VIEW_CD
	, SORT_PATH
	, LIST_ID
	, SRVC_LIST_ID
	, UP_SRVC_VIEW_CD
	, LIST_CM
	, PUB_SE
	, LIST_NM
	, LIST_PUB_NM
	, LIST_ENG_NM
	, ROW_NUMBER() OVER(ORDER BY SORT_PATH) AS LIST_SN 
	, TBL_ID
	, TBL_NM
	, PUB_CNT
	, LV
	, EXP
	, OPEN
FROM CTE
ORDER BY SORT_PATH;

 

 

MySQL의 WITH RECURSIVE 기반 계층 쿼리로 바꿔보자. 

 

먼저 LST 임시테이블을 정의해 두 테이블을 합친 집합을 만들고 WITH RECURSIVE 로 만든 CTE 임시테이블으로 LST 임시테이블을 재귀적으로 참조한다. 

 

START WITH / CONNECT BY PRIOR 구문은 WITH RECURSIVE와 JOIN 조건을 대체되고, LEVEL 컬럼은 CTE 내부 LV로 대체해서 사용한다.

 

MySQL에서는 ROWNUM을 사용을 사용해서 각 행에 순차적 번호를 할당할 수 없으니 Oracle의 ORDER SIBLINGS BY 구문을 MySQL에서 구현하기 위해 SORT_PATH를 도입하고, 최종 SELECT 구문에서 ROW_NUMBER 윈도우 함수를 사용해 LIST_SN을 넘버링한다.

 

계층 쿼리에서 같은 레벨에 속한 형제 노드들을 특정 기준으로 정렬할 때 SORT_PATH를 도입한다.

각 단계의 정렬 기준을 누적해 전체 경로를 SORT_PATH에 저장하고 해당 컬럼으로 ORDER SIBLINGS BY 구문을 구현한다.

 

 

반응형
저작자표시 (새창열림)

'💡 솔루션' 카테고리의 다른 글

localhost 브라우저에서 쿠키 제대로 받기  (0) 2025.06.25
[SQL Server] 암호화 된 View 다루기  (1) 2025.01.03
[Nginx] 리버스 프록시 서버 구축  (1) 2024.11.13
[Tomcat] 네트워크 드라이브 권한 관련 오류  (0) 2024.11.09
[PDF.js] PDF.js 완벽 가이드  (3) 2024.11.07

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • localhost 브라우저에서 쿠키 제대로 받기

    localhost 브라우저에서 쿠키 제대로 받기

    2025.06.25
  • [SQL Server] 암호화 된 View 다루기

    [SQL Server] 암호화 된 View 다루기

    2025.01.03
  • [Nginx] 리버스 프록시 서버 구축

    [Nginx] 리버스 프록시 서버 구축

    2024.11.13
  • [Tomcat] 네트워크 드라이브 권한 관련 오류

    [Tomcat] 네트워크 드라이브 권한 관련 오류

    2024.11.09
다른 글 더 둘러보기

정보

천천히 꾸준히 조용히 블로그의 첫 페이지로 이동

천천히 꾸준히 조용히

  • 천천히 꾸준히 조용히의 첫 페이지로 이동

검색

방문자

  • 전체 방문자
  • 오늘
  • 어제

카테고리

  • 분류 전체보기 (674)
    • Algorithm (205)
      • Data Structure (5)
      • Theory && Tip (33)
      • Baekjoon (166)
      • ALGOSPOT (1)
    • Spring (123)
      • Spring (28)
      • Spring Web MVC (20)
      • Spring Database (14)
      • Spring Boot (6)
      • Spring 3.1 (11)
      • Spring Batch (6)
      • Spring Security (16)
      • JPA (12)
      • Spring Data JPA (5)
      • QueryDSL (4)
      • eGovFramework (1)
    • Programming Language (74)
      • C (25)
      • C++ (12)
      • Java (19)
      • JavaScript (15)
      • Python (1)
      • PHP (2)
    • Computer Science (142)
      • Machine Learning (38)
      • Operating System (18)
      • Computer Network (28)
      • System Programming (22)
      • Universial Programming Lang.. (8)
      • Computer Architecture (4)
      • Compiler Design (11)
      • Computer Security (13)
    • Database (21)
      • Database (7)
      • MySQL (3)
      • Oracle (3)
      • Redis (5)
      • Elasticsearch (3)
    • DevOps (20)
      • Docker && Kubernetes (8)
      • Jenkins (4)
      • Amazon Web Service (8)
    • Mobile (28)
      • Android (21)
      • Flutter (7)
    • 💡 솔루션 (17)
    • 👥 모각코 (8)
    • 💬 기록 (6)
    • 📚 공부 (5)
    • -------------- (25)

최근 글

나의 외부 링크

메뉴

  • 홈
반응형

정보

i3months의 천천히 꾸준히 조용히

천천히 꾸준히 조용히

i3months

블로그 구독하기

  • 구독하기
  • RSS 피드

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © i3months.

티스토리툴바