DB/Database
[MySQL] 계층형 조회 (Recursive 재귀 쿼리)
Nellie Kim
2024. 1. 9. 11:16
728x90
WITH RECURSIVE cte_count
AS (
-- Non-Recursive 문장( 첫번째 루프에서만 실행됨 )
SELECT 1 AS n
UNION ALL
-- Recursive 문장(읽어 올 때마다 행의 위치가 기억되어 다음번 읽어 올 때 다음 행으로 이동함)
SELECT n + 1 AS num
FROM cte_count
WHERE n < 3
)
SELECT * FROM test;
- 메모리 상에 가상의 테이블을 저장한다.
- 반드시 UNION 사용해야한다.
- 반드시 비반복문(Non-Recursive)도 최소한 1개 요구된다. 처음 한번만 실행
- SubQuery에서 바깥의 가상의 테이블을 참조하는 문장(반복문)이 반드시 필요하다.
- 반복되는 문장은 반드시 정지조건(Termination condition)이 요구된다.
- 가상의 테이블을 구성하면서 그 자신(가상의 테이블)을 참조하여 값을 결정할 때 유용한다.
<select id="getRoleMenuScrnRoleUse" parameterType="java.util.List"
resultType="kr.co.common.spring.security.dto.RoleMenuScrnMapg">
-- 메뉴 정보 및 계층 구조를 조회하는 SQL 쿼리
SELECT
RESULT.MENU_ID, -- 메뉴 아이디
NVL2(RESULT.HPOS_MENU_ID, RESULT.HPOS_MENU_ID, '#') AS HPOS_MENU_ID, -- 상위 메뉴 아이디
RESULT.MENU_IDX, -- 메뉴 순서
RESULT.MENU_ORDER, -- 메뉴 트리 정렬 순서
RESULT.MENU_NM, -- 메뉴 명
RESULT.LVL, -- 레벨
RESULT.MENU_PATH, -- 메뉴 경로
RESULT.MENU_URL -- 메뉴 링크
FROM (
-- 재귀적인 CTE (Common Table Expression)를 사용하여 계층적 구조를 조회
WITH RECURSIVE CTE AS (
-- 초기 쿼리: 최상위 메뉴부터 시작하여 계층적으로 조회
SELECT
MNU.MENU_ID,
MNU.MENU_NM,
MNU.HPOS_MENU_ID,
1 AS LVL,
MNU.MENU_NM AS MENU_PATH,
TO_CHAR(MNU.MENU_IDX) AS MENU_ORDER,
MNU.MENU_PATH AS MENU_URL,
MNU.MENU_IDX
FROM
TB_MENU MNU
INNER JOIN (
-- 특정 역할에 속한 메뉴 조회
SELECT
MAP.MENU_ID
FROM
TB_AUTH_MENU_MAP MAP
INNER JOIN
TB_AUTH AUTH ON MAP.AUTH_ID = AUTH.AUTH_ID
WHERE
MAP.AUTH_ID IN
<foreach collection="list" item="item" index="index" separator="," open="(" close=")">
#{item.roleId}
</foreach>
GROUP BY MAP.MENU_ID
) USERMENUS ON MNU.MENU_ID = USERMENUS.MENU_ID
WHERE
HPOS_MENU_ID IS NULL
UNION ALL
-- 재귀 쿼리: 하위 메뉴들을 계속해서 조회하여 계층적 구조 생성
SELECT
B.MENU_ID,
B.MENU_NM,
B.HPOS_MENU_ID,
C.LVL + 1 AS LVL,
CONCAT(C.MENU_PATH,' > ', B.MENU_NM) AS MENU_PATH,
CONCAT(C.MENU_ORDER,',', B.MENU_IDX) AS MENU_ORDER,
B.MENU_PATH AS MENU_URL,
B.MENU_IDX
FROM
TB_MENU B
INNER JOIN (
-- 특정 역할에 속한 메뉴 조회
SELECT
MAP.MENU_ID
FROM
TB_AUTH_MENU_MAP MAP
INNER JOIN
TB_AUTH AUTH ON MAP.AUTH_ID = AUTH.AUTH_ID
WHERE
MAP.AUTH_ID IN
<foreach collection="list" item="item" index="index" separator="," open="(" close=")">
#{item.roleId}
</foreach>
GROUP BY MAP.MENU_ID
) USERMENUS ON B.MENU_ID = USERMENUS.MENU_ID
INNER JOIN CTE C ON B.HPOS_MENU_ID = C.MENU_ID
)
-- 최종 결과: CTE에서 계산된 메뉴 정보를 선택
SELECT
MENU_ID, -- 메뉴 아이디
MENU_NM, -- 메뉴 명
HPOS_MENU_ID, -- 상위 메뉴 아이디
LVL, -- 레벨
MENU_PATH, -- 메뉴 경로
MENU_ORDER, -- 메뉴 트리 정렬 기준
MENU_URL, -- 메뉴 URL
MENU_IDX -- 메뉴 순서
FROM CTE A
) RESULT
-- 메뉴와 권한 매핑 정보를 결합하여 특정 역할이 갖고 있는 메뉴를 조회
LEFT OUTER JOIN
TB_AUTH_MENU_MAP MAP ON RESULT.MENU_ID = MAP.MENU_ID
AND MAP.AUTH_ID IN
<foreach collection="list" item="item" index="index" separator="," open="(" close=")">
#{item.roleId}
</foreach>
-- 결과를 메뉴 순서(MENU_IDX)로 오름차순 정렬
ORDER BY RESULT.MENU_IDX ASC;
출처: https://inpa.tistory.com/entry/MYSQL-📚-RECURSIVE-재귀-쿼리 [Inpa Dev 👨💻:티스토리]