쿼리를 짜다보면 트리 구조 형태로 데이터를 만들어야 하는 경우가 종종있다. 이를 경우 MSSQL에서 사용할 수 있는 방법이 있다. 바로 재귀 쿼리 이다. 재귀적 CTE의 기본 형식은 다음과 같다.
WITH CTE_RECURSIVE(column_name[,...n]) AS ( <쿼리문1: SELECT * FROM TABLE_A // Anchor member UNION ALL <쿼리문2: SELECT * FROM TABLE_A JOIN CTE_테이블 이름 // Recursive member ) -- Statement using the CTE -- SELECT * FROM CTE_RECURSIVE
재귀 실행의 의미 체계는 다음과 같습니다.
1. CTE 식을 앵커 멤버와 재귀 멤버로 분할합니다. 2. 앵커 멤버를 실행(쿼리문1)하여 첫 번째 호출 또는 기본 결과 집합(T0)을 만듭니다. 3. Ti는 입력으로 사용하고 Ti+1은 출력으로 사용하여 재귀 멤버(쿼리문2)를 실행합니다. 4. 빈 집합이 반환될 때까지 3단계를 반복합니다. 5. 결과 집합을 반환합니다. 이것은 T0에서 Tn까지의 UNION ALL입니다.
지금부터 예제를 통해 적용해보자.
아래는 트리구조로 만든 임시 테이블이다.
WITH TEST(UPPER_CODE,CODE,NAME) AS (
SELECT NULL,10, 'LEVEL1'
UNION ALL
SELECT '10','1001','LEVEL2'
UNION ALL
SELECT NULL,20, 'LEVEL1'
UNION ALL
SELECT '20','2001','LEVEL2'
UNION ALL
SELECT '2001','2001001','LEVLE3'
)
SELECT * FROM TEST
이렇게 출력 될 수 있도록 말이다. 그럼 우선, 위 쿼리 실행 결과를 뷰 테이블로 만들고 다시 재귀 쿼리를 이용해야 한다. Microsoft SQL Server Management Studio에서 View 테이블을 직접 만들어도 되지만, 여기서 공부하는 용으로 정리하는 것이니 WITH AS 을 이용하여 위 데이터를 가지는 임시 vm_table을 만들어 사용했다.
WITH VM_CTE(UPPER_CODE,CODE,NAME,LEVEL) AS ( SELECT NULL, '10', 'ITEM10', 1 UNION ALL SELECT NULL, '20' ,'ITEM20', 1 UNION ALL SELECT '20', '2001' ,'ITEM12001', 2 UNION ALL SELECT '2001', '2001001', 'ITEM2001001', 3 UNION ALL SELECT '10', '1001', 'ITEM101001', 2 UNION ALL SELECT '1001', '1001001', 'ITEM101001', 3 ) SELECT * FROM VM_CTE
이 뷰테이블 VM_CTE를 이용하여 작성한 쿼리와 실행 결과를 아래에 정리해두었다. 참고하기 바란다.
WITH VM_CTE(UPPER_CODE,CODE,NAME,LEVEL) AS ( SELECT NULL, '10', 'ITEM10', 1 UNION ALL SELECT NULL, '20' ,'ITEM20', 1 UNION ALL SELECT '20', '2001' ,'ITEM2001', 2 UNION ALL SELECT '2001', '2001001', 'ITEM2001001', 3 UNION ALL SELECT '10', '1001', 'ITEM1001', 2 UNION ALL SELECT '1001', '1001001', 'ITEM1001001', 3 ) ,CTE_RESURSIVE AS ( SELECT CODE, CODE CODE1, CASE WHEN 1=2 THEN CODE ELSE NULL END CODE2, CASE WHEN 1=2 THEN CODE ELSE NULL END CODE3, NAME NAME1, CASE WHEN 1=2 THEN NAME ELSE NULL END NAME2, CASE WHEN 1=2 THEN NAME ELSE NULL END NAME3, LEVEL FROM VM_CTE WHERE UPPER_CODE IS NULL
UNION ALL SELECT SC.CODE, CC.CODE1 CODE1, CASE WHEN SC.LEVEL = 1 THEN NULL WHEN SC.LEVEL = 2 THEN SC.CODE WHEN SC.LEVEL = 3 THEN CC.CODE2 END CODE2, CASE WHEN SC.LEVEL = 1 THEN NULL WHEN SC.LEVEL = 2 THEN NULL WHEN SC.LEVEL = 3 THEN SC.CODE END CODE3 , CC.NAME1 NAME1, CASE WHEN SC.LEVEL = 1 THEN NULL WHEN SC.LEVEL = 2 THEN SC.NAME WHEN SC.LEVEL = 3 THEN CC.NAME2 END NAME2, CASE WHEN SC.LEVEL = 1 THEN NULL WHEN SC.LEVEL = 2 THEN NULL WHEN SC.LEVEL = 3 THEN SC.NAME END NAME3 , SC.LEVEL FROM VM_CTE SC INNER JOIN CTE_RESURSIVE CC ON SC.UPPER_CODE=CC.CODE WHERE UPPER_CODE IS NOT NULL ) SELECT * FROM CTE_RESURSIVE
WITH VM_CTE(UPPER_CODE,CODE,NAME,LEVEL) AS (
SELECT NULL, '10', 'ITEM10', 1
UNION ALL
SELECT NULL, '20' ,'ITEM20', 1
UNION ALL
SELECT '20', '2001' ,'ITEM2001', 2
UNION ALL
SELECT '2001', '2001001', 'ITEM2001001', 3
UNION ALL
SELECT '10', '1001', 'ITEM1001', 2
UNION ALL
SELECT '1001', '1001001', 'ITEM1001001', 3
) ,CTE_RESURSIVE AS (
SELECT CODE,
CODE CODE1,
CASE WHEN 1=2 THEN CODE
ELSE NULL END CODE2,
CASE WHEN 1=2 THEN CODE
ELSE NULL END CODE3,
NAME NAME1,
CASE WHEN 1=2 THEN NAME
ELSE NULL END NAME2,
CASE WHEN 1=2 THEN NAME
ELSE NULL END NAME3,
LEVEL
FROM VM_CTE
WHERE UPPER_CODE IS NULL
UNION ALL
SELECT SC.CODE,
CC.CODE1 CODE1,
CASE WHEN SC.LEVEL=1 THEN NULL
WHEN SC.LEVEL=2 THEN SC.CODE
WHEN SC.LEVEL=3 THEN CC.CODE2
END CODE2,
CASE WHEN SC.LEVEL= 1 THEN NULL
WHEN SC.LEVEL = 2 THEN NULL
WHEN SC.LEVEL = 3 THEN SC.CODE
END CODE3 ,
CC.NAME1 NAME1,
CASE WHEN SC.LEVEL=1 THEN NULL
WHEN SC.LEVEL=2 THEN SC.NAME
WHEN SC.LEVEL=3 THEN CC.NAME2
END NAME2,
CASE WHEN SC.LEVEL= 1 THEN NULL
WHEN SC.LEVEL = 2 THEN NULL
WHEN SC.LEVEL = 3 THEN SC.NAME
END NAME3 ,
SC.LEVEL
FROM VM_CTE SC
INNER JOIN CTE_RESURSIVE CC ON SC.UPPER_CODE=CC.CODE
WHERE UPPER_CODE IS NOT NULL
)
SELECT *
FROM CTE_RESURSIVE
<<실행결과 >>
위 쿼리를 잠깐 살펴보자.
재귀 쿼리는 Anchor 멤버를 먼저 실행후 ,T0을 만든다고 했다. 그래서 위 쿼리의 Anchor멤버를 실행하게 되면 T0가 생성이 된다.
T0
CODE
CODE1
CODE2
CODE3
NAME1
NAME2
NAME3
LEVEL
10
10
NULL
NULL
ITEM10
NULL
NULL
1
20
20
NULL
NULL
ITEM20
NULL
NULL
1
T1
T0가 입력으로 사용되어 Recursive멤버가 실행되게 된다. 위 쿼리에서 Recursive 멤버인 아래 쿼리가 실행이 되는 것이다.
쿼리를 작성하다 보면 숫자와 문자가 뒤섞인 데이터들이 많이 있다. 이런 데이터들을 order by 하면 내가 원하는 순서대로 오름차순 또는 내림차순이 되지 않는 경우가 있다. 예를 들어 아래 쿼리를 보자. 내림차순 정렬을 했지만, 출력되는 순서는 5S,11S,10S인걸 알수 있다. 이처럼 데이터 정렬이 제대로 안될경우 해결 방법이 있다.
이를 경우는 order by Len( column_Name) 을 사용하면 된다. 아래처럼 원하는 순서로 출력되는 것을 볼 수 있다.