프로젝트 리펙토링을 하면서 oracle로 구현했던 계층형 게시판을 mysql로 다시 구현해보고자 시도했다가 문제가 발생.
기존 Oracle에서 구현할때는 start with ~ connect by 를 활용해 아주 간단하게 처리했다.
이때 완전 착각했던게 '원글 글번호를 토대로 아래에 UpperNo가 동일한 데이터를 찾아 하위에 넣어주면 되겠네. MySQL에서도 그냥 이렇게 하면 되겠다' 라고 생각한점이다.
그래서 솔직히 mysql에서 다시 구현 해봐야지 생각만 했지 미루고 있다가 막상 하려 했더니 MySQL에는 start with ~ connect by를 사용을 못하네...?
그래서 이것저것 조인도 해보고 컬럼 추가도 해보고 삭제도 해보고 별걸 다해보고 고민해봤지만 방법이 딱 하나 떠올랐다.
step이라는 컬럼을 생성해 소수점 형태로 처리하는 방법.
원글은 0, 첫 답글은 0.1, 두번째 답글은 0.2, 첫답글의 답글은 0.11, 두번째글의 답글은 0.21 이런형태로.
이전에도 비슷한 방법으로 구현한적이 있었는데 그때는 아예 step 컬럼이 하위 계층의 순서를 정의하는 역할을 했었다.
그러다보니 중간에 넣어줘야 하는 경우는 뒷번호들을 모두 하나씩 밀어야 하는 경우가 생겨 해놓고도 비효율적이라는 생각을 했었던 방법이다.
요즘 사이트들을 보면 계층형태더라도 한 계층 정도만 나오거나 몇계층 안나오는걸 많이 보긴 했는데 그런 경우에는 사용해도 괜찮겠지만 이 경우는 계층에 제한이 없다는 전제하에 만들었던 거라...........
이렇게 소수점 형태로 처리하면 그냥 GroupNo만들어서 역순으로 정렬하고 step 순서로 출력하게 하면 잘 나오긴한다.
근데 문제는 글 등록할때마다 그럼 step값을 제대로 넣어줄 수 있어야 하고 그럼 자기 자신의 위치를 찾기 위해 조회를 한번 더 해야하는 경우가 발생할것 이라고 생각했다.
한 이틀 내리 그냥 이것저것 쿼리문 계속 만들어보고 돌려보고 하다가 도저히 안되겠어서 검색해봤더니 함수를 이용한 처리와 재귀를 이용한 처리가 있다는 것을 알게 되었다.
전에는 못보던 글들인데 아무래도 그때는 'mysql 계층형 게시판' 이렇게만 검색해서 안나왔었던것 같다.
그래서 '이방법은 알아두면 좋겠다.' , '써먹을 수 있는 방법이겠다' 라는 생각이 드는 방법이 함수랑 재귀 쿼리다.
함수를 이용한 처리의 단점으로는 구현이 복잡하다는 것이 단점이고 재귀 쿼리를 사용하는 방법은 mysql 5.7 이하 버전에서는 사용할 수 없다는 것과 테이블의 모든 행 개수만큼 반복하기 때문에 데이터가 많을수록 효율이 떨어진다.
나중에 다 까먹었을때 다시 보더라도 빠르게 이해할 수 있도록 최대한 자세하게 작성했으니
혹시나 보시는 분들 있으시면 알고계신 부분들은 넘기시면서 보시는거 추천..
데이터는 이렇게 존재한다. 물론 뭐 이 위로도 1~73까지 있긴하지만 계층형 구현을 하는 부분은 이부분 밖에 없기 때문에...
그럼 여기서 순서는 75 -> 76 -> 77 -> 78 -> 79 -> 81 -> 82 -> 84 -> 80 이순서로 계층이 완성되어야 한다.
방법 1. 함수
-- 함수
DROP FUNCTION IF EXISTS fnc_hierarchi; -- fnc_hierarchi라는 함수가 존재하면 drop
DELIMITER $$
CREATE FUNCTION fnc_hierarchi() RETURN INT -- fnc_hierarchi() 함수 생성 및 이 함수의 리턴 타입은 int
NOT DETERMINISTIC -- Stored routine을 매번 새로 호출해서 비교. 비교되는 레코드 수 만큼 호출 발생.
READS SQL DATA -- 함수가 데이터를 변경하지 않도록 한다.
BEGIN
DECLARE v_id int; -- 글번호(boardNo) 변수
DECLARE v_parent int; -- 상위글 번호(upperNo) 변수
DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL; -- 마지막 레코드에 도달하면 @id를 null로 변환
SET v_parent = @id; -- v_parent값을 @id 값으로 변환
SET v_id = -1; -- v_id를 -1로 초기화
IF @id IS NULL THEN -- 일반적인 if문과 동일. id가 null일 경우 아래 return을 수행.
RETURN NULL;
END IF;
LOOP -- 반복문
SELECT MIN(boardNo)
INTO @id
FROM board
WHERE boardUpperNo = v_parent
AND boardNo > v_id; -- 조회 결과를 @id에 복사
IF(@id IS NOT NULL) OR (v_parent = @start_with) THEN
SET @level = @level + 1;
RETURN @id;
END IF;
SET @level := @level - 1;
SELECT boardNo, boardUpperNo
INTO v_id, v_parent
FROM board
WHERE boardNo = v_parent; -- 조회된 결과를 v_id = boardNo, v_parent = boardUpperNo로 넣어줌.
END LOOP; -- 반복문 끝
END $$
DELIMITER;
-- Query
SELECT b.boardNo
, CASE WHEN LEVEL-1 > 0 THEN CONCAT(CONCAT(REPEAT(' ', level-1),'ㄴ'), b.boardTitle)
ELSE b.boardTitle
END AS boardTitle
, b.boardUpperNo
, b.boardGroupNo
FROM(
SELECT fnc_hierarchi() AS id
, @level AS level
FROM(
SELECT @start_with:=0
, @id=@start_with
, @level:=0
) vars
JOIN board
WHERE @id IS NOT NULL
) fnc
JOIN board b ON fnc.id = b.boardNo
;
이렇게 작성했다.
mysql에서 함수 작성이 가능한지도 몰랐기 때문에 처음보는 부분이 많았다.
일단 문법을 먼저 뜯어봤다.
DROP FUNCTION IF EXISTS fnc_hierarchi;
이 부분의 경우는 그냥 보자마자 이해할 수 있는 부분이었는데 fnc_hierarchi라는 함수가 존재한다면 DROP 해준다.
DELIMITER $$
보통 쿼리에서 ; 로 쿼리문을 마무리하게 되는데 이것을 $$로 바꾼다는 것이다.
이걸 설정하지 않으면 문장을 구분하기 어렵기 때문에 세미콜론이 아닌 $$로 변경하는 것이고
그래서 함수 마지막에 DELIMITER ; 로 다시 세미콜론을 사용하도록 되돌리는 것이다.
CREATE FUNCTION fnc_hierarchi() RETURNS INT
이 부분 역시 그냥 보이는 그대로 fnc_hierarchi라는 함수를 생성할것이고 이 함수의 반환타입은 INT라고 정의하는 부분이다.
이때 ( ) 안에는 파라미터가 들어갈 수도 있다.
여기서는 굳이 필요가 없어서 안넣었는데 fnc_hierarchi(boardNo INT) 이런식으로 사용할 수 있다.
NOT DETERMINISTIC
이 옵션의 경우는 Stored routine의 결과값이 계속 달라진다고 가정하고, 비교가 실행되는 레코드마다 이 Stored routine을 매번 새로 호출해 비교를 실행하도록 하는것이다.
기본 default가 NOT DETERMINISTIC이고 DETERMINISTIC 옵션은 동일한 입력 매개변수에 대해 항상 동일한 결과를 생성한다고 가정하고 1번만 함수를 호출하도록 한다.
한 포스팅에서 본것을 그대로 예를 들자면 데이터가 2,702,270개가 존재한다고 했을 때
함수에서는 2,700,000을 리턴하도록 작성했다면
1~ 2,702,700의 값을 갖고 있는 a 컬럼이 이 함수의 리턴값 보다 큰 경우를 count(*)하도록 하는 쿼리를 작성한다고 하자.
그럼 NOT DETERMINISTIC은 a가 1일때 함수를 호출해 2,700,000을 리턴받아 a와 비교를 하고,
a가 2일때 함수를 또 호출해 리턴받은 값과 비교하는 방법으로 처리하기 때문에
함수를 레코드 수만큼 호출하게 된다.
하지만 DETERMINISTIC 옵션으로 설정한다면 a가 1일때 함수를 호출해 2,700,000을 리턴받아 비교했으면
a가 2일때는 함수를 호출하지 않고 이전 리턴값인 2,700,000과 그대로 비교하게 된다.
그럼 함수는 레코드 수만큼 호출되는 것이 아닌 처음 한번만 호출되고 그 뒤로는 비교처리만 하게 되는것이다.
잘 설명해주신 분이 있어서 그건 Reference에서 확인할것.
이 함수에서는 하위 글에 대한 조회를 계속하고 리턴값이 계속 변하기 때문에 NOT DETERMINISTIC으로 모든 레코드수 만큼 호출되도록 해야 한다.
READS SQL DATA
함수가 데이터를 변경하지 않는다는 것을 나타내는 것이다.
이 옵션은 CONTAINS SQL, NO SQL, READS SQL DATA, MODIFIES SQL DATA 이렇게 네가지 종류가 존재한다.
함수가 데이터를 읽거나 또는 쓰는 정보를 제공하는 옵션이고 default는 CONTAIN SQL이기 때문에 하나를 명확히 지정해서 사용하는 것이 좋다.
이 함수에서는 함수가 데이터를 변경해야 할 이유가 없으므로 READS SQL DATA로 설정한다.
그리고 CREATE FUNCION 명령문이 default로 수용되도록 하기 위해서는 DETERMINISTIC 옵션이나 NO SQL 및 READS SQL DATA 중에 한개는 반드시 확실하게 명시해야 한다.
그렇지 않으면
ERROR 1418 : This function has none of DETERMINISTIC, NO SQL or READS SQL DATA in its declaration and binary logging is enabled
이런 오류가 발생한다.
READS SQL DATA가 뭔지 알아보기 위해 검색했을때 다 이 오류에 대한 포스팅 밖에 안나왔다...
그나마 포스팅 한개를 찾았으니 자세한건 아래 Reference에서 확인.
BEGIN, END
구현부의 시작과 끝을 명시하는것이다.
DECLARE
변수를 의미한다.
그래서 이 함수에서는 v_id라는 int형 변수와 v_parent라는 int형 변수를 생성한 것이다.
마지막 CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;
이 부분의 경우는 좀 다르다.
handler를 명시한건데 CONTINUE는 핸들러의 begin ~ end 부분을 실행하고 남은 본문을 이어서 수행하도록 하는 것이다.
그리고 not found는 커서가 마지막 레코드에 도달해 다음 레코드를 fetch하지 못했을때의 상황을 의미한다.
마지막 레코드에 도달했다면 SET @id = NULL; 로 @id 값으로 NULL을 넣어주라는 것이다.
여기서 핸들러는 try catch로 exception 처리를 하는것과 동일하다고 볼 수 있다.
IF
보이는 그대로 if문과 동일한 조건문이다.
IF로 시작하고 조건이 끝나 처리부분에 들어가려면 THEN으로 맺음을 해준다.
if(@id == null)
return null;
이 상태와 같다고 보면 되고 if문이 끝나는 부분에서는 END IF로 끝났음을 알려야 한다.
LOOP
반복문이다.
LOOP역시 끝나는 부분에서 END LOOP로 끝났음을 알려야 한다.
SELECT ~ INTO ~
조회된 결과를 복사한다.
함수 내용으로 보면 MIN(boardNo)로 가장 작은 boardNo를 가져와 @id에 복사해주는 것이다.
함수 마지막 조회쿼리도 보면
SELECT boardNo, boardUpperNo INTO v_id, v_parent
이렇게 되어있는데
그럼 보이는 그대로 v_id에는 boardNo가 v_parent에는 boardUpperNo가 들어가게 된다.
사용자 정의 변수
함수 말고 쿼리문에서 확인해보면 @id, @start_with, @level 이런것들을 볼 수 있다.
이게 사용자 정의 변수다.
말그대로 내가 변수를 생성해 사용하는 방법이 되는것이고
여기에 값을 넣어주는 방법으로는 SET과 := 가 있다.
SET은 함수에서처럼 SET @id = 0 이런식으로 사용할 수 있다.
하지만 SET을 제외한 명령문에서는 = 가 비교연산자로 취급을 받기 때문에 :=로 값을 대입해야 한다.
@id:=0 이런식으로.
물론 SET에서도 SET @id:=0 이렇게 사용하는것도 가능하다.
여기서 저장하는 값에 의해 자료형이 정해지며 Integer, Decimal, Float, Binary, 문자열타입만 취급할 수 있다.
또한 변수를 초기화 하지 않은 경우의 값은 NULL, 자료형은 String 타입이 된다.
처리과정
SELECT fnc_hierarchi() AS id, @level AS level
FROM(
SELECT @start_with:=0, @id:=@start_with, @level:=0
) vars
JOIN board
WHERE @id IS NOT NULL
쿼리문에서 일단 이 부분을 먼저 보자면 제일 안쪽 select 에서 start_with를 0으로 잡고 @id는 @start_with를 그대로 받았으니 동일하게 0, @level 역시 0이다.
그럼 함수에서 보면 SET v_parent = @id; 이런 부분이 있다.
이때 @id가 저기 쿼리문에 있는 @id이다.
그래서 처음 함수를 호출했을 때 @id는 0이 되어 들어가게 된다.
중간중간에 있는 @level역시 쿼리문에 존재하는 level의 값이 들어간다.
함수 위에서부터 쭉 내려가보면
v_parent과 v_id에 값을 SET 해주고
IF문에서는 @id가 null이 아니기 때문에 넘어가게 된다.
그럼 이제 LOOP를 타게 되는데
테이블에서 상위 글번호를 의미하는 boardUpperNo와 v_parent가 동일하고 boardNo가 v_id인 -1보다 큰 데이터를 조건으로 찾고 있으므로 제일 작은 1번 글의 boardNo 가 @id로 복사된다.
그럼 여기까지 각 변수들 값을 확인해보면
@id = 1, v_parent = 0, v_id = -1, level = 0이다.
LOOP 안에 있는 IF문을 보면 id가 null이 아니거나 v_parent = @start_with 인 조건인데
id는 null이 아니고 v_parent는 0이기 때문에 조건에 만족한다.
그래서 level + 1로 level이 1이 되고 그대로 @id를 리턴한다.
여기까지의 값들은
@id = 1, level = 1 이다.
2번째 호출
다시 함수가 호출이 될때 새로 @id가 0이 들어오는 것이 아닌 1이 들어오게 되고
그럼 v_parent = 1, v_id = -1이 된다.
첫 IF문은 역시 뛰어넘게 되고 LOOP에 들어가 select에서 조회를 하는데
boardUpperNo = 1인것이 없기 때문에 null이 되고 @id는 null을 갖게 된다.
그럼 다음 if문에서 @id는 null이고 v_parent = 1이니까 0인 @start_with와 다르기 때문에 false로 빠지게 된다.
여기서 level을 감소시킨 뒤에 v_id = boardNo, v_parent = boardUpperNo
이렇게 값을 넣어주게 되는데
WHERE boardNo = v_parent이기 때문에 v_id가 v_parent가 되고 해당 데이터의 upperNo가 v_parent가 된다.
그래서 v_id = 1, v_parent는 0이 된다.
여기서 LOOP를 끝내는 조건이 없기 때문에 함수 제일 위로 올라가는 것이 아닌 LOOP문 제일 위로 올라가게 되고
select에서 이 값을 그대로 갖고 처리한다.
그럼 UpperNo = v_parent는 0이니 만족하고 boardNo > 1 이기 때문에 바로 다음 데이터인 2번 글의 boardNo가 @id에 복사되게 되고 다음 if문도 만족해 @id = 2, @level = 1이 된다.
이렇게 설명한 이유는 처리과정때문인데
처음 받아서 첫 글을 확인해 @id에 복사한 뒤에 다시 함수가 호출되었을때 하위글을 찾는 과정이기 때문이다.
만약 이 두번째 호출에서 1번글의 하위글이 있었다면?
LOOP에 들어와 첫 조회쿼리에서 boardUpperNo = 1 and boardNo > -1이기 때문에
하위 글이 @id에 복사되게 되었을 것이다.
근데 존재하지 않았기 때문에 null이 되었고 조건문 또한 통과하지 못해 LOOP에 갇히게 되며
그럼 하위 글이 없다고 봐야하므로 level을 다시 감소시키는 것이다.
그리고 마지막에 현재 @id보다 큰 수를 갖는 boardNo를 찾아야 하기 때문에 v_id에 boardNo를 넣어주고
@id가 boardNo인 데이터는 UpperNo가 설계한 제일 default 값일 것이므로 v_parent에 넣어 다음 원글을 찾을 수 있도록 하는것이다.
다시 처리 순서를 정리.
1~84번글까지 존재하고 답글이 달려있는 글은 위 이미지의 데이터만 존재한다고 가정.
그럼 처음 1번글에 대해 조회하고 @id에 boardNo를 넣어준다.
boardNo = 1, @id = 1
그리고 하위데이터인 답글이 존재하는지 확인
where boardUpperNo = 1 == false
존재하지 않기 때문에 다음글을 조회
boardNo = 1, @id = 1
boardNo = 2, @id = 2
이 데이터 역시 하위데이터가 존재하는지 확인
where boardUpperNo = 2 == false
다음데이터 조회
boardNo = 1, @id = 1
boardNo = 2, @id = 2
boardNo = 3, @id = 3
......
boardNo = 75, @id = 75
이렇게 75번글까지 계속 반복.
하위데이터 확인
where boardUpperNo = 75 == true
이때 조회된 데이터는 75보다 큰 데이터중 가장 작은 데이터이기 때문에 76번글을 조회해 @id에 복사.
boardNo = 75, @id = 75
boardNo = 76, @id = 76
동일하게 하위 데이터 조회
where boardUpperNo = 77 == true
boardNo = 75, @id = 75
boardNo = 76, @id = 76
boardNo = 77, @id = 77
boardNo = 78, @id = 78
78번까지 이런식으로 조회하고 이 하위데이터는 없기 때문에 다시 상위로 이동.
where boardUpperNo = 77 == false
false이기 때문에 다시 한번 더 상위로 이동
where boardUpperNo = 76 == true
boardNo = 79, @id = 79
....
이런 순서로 계속 쌓아나간다.
그래서 @id가
1, 2, 3 ..... 75, 76, 77, 78, 79, 80, 81, 82, 84, 83
이렇게 쌓여나가게 되고 이걸 기준으로 정렬되어 출력되기 때문에 계층형이 완성된다.
여기까지는 잘 해결했으나 보통의 게시판처럼 마지막에 작성한 글이 제일 위에 있도록 내림차순 정렬을 하게 되면
이 계층형이 틀어지는 문제가 발생했다.
추가한건 쿼리문에서 ORDER BY boardGroupNo desc 한줄.
그래서 id도 다시 정렬하게 해줘야 하나? 라는 생각에
ORDER BY boardGroupNo desc, id asc
이렇게 바꿔봤더니
이번에는 80번 글이 중간에 들어가버린다..
너무 자연스러운 위치에 들어가서 속을뻔..........
그래서 생각을 조금 바꿔봤다.
아예 순서를 정해놓고 정렬하면 될거같은데??
그래서 함수랑 쿼리를 좀 수정했다.
-- 함수
DROP FUNCTION IF EXISTS fnc_hierarchi;
DELIMITER $$
CREATE FUNCTION fnc_hierarchi() RETURN INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE v_id int;
DECLARE v_parent int;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;
SET v_parent = @id;
SET v_id = -1;
IF @id IS NULL THEN
RETURN NULL;
END IF;
LOOP
SELECT MIN(boardNo)
INTO @id
FROM board
WHERE boardUpperNo = v_parent
AND boardNo > v_id;
IF(@id IS NOT NULL) OR (v_parent = @start_with) THEN
SET @level = @level + 1;
SET @step = @step + 1;
RETURN @id;
END IF;
SET @level := @level - 1;
SELECT boardNo, boardUpperNo
INTO v_id, v_parent
FROM board
WHERE boardNo = v_parent;
END LOOP;
END $$
DELIMITER;
-- Query
SELECT b.boardNo
, CASE WHEN LEVEL-1 > 0 THEN CONCAT(CONCAT(REPEAT(' ', level-1),'ㄴ'), b.boardTitle)
ELSE b.boardTitle
END AS boardTitle
, b.boardUpperNo
, b.boardGroupNo
FROM(
SELECT fnc_hierarchi() AS id
, @level AS level, @step AS step
FROM(
SELECT @start_with:=0
, @id=@start_with
, @level:=0
, @step:=0
) vars
JOIN board
WHERE @id IS NOT NULL
) fnc
JOIN board b ON fnc.id = b.boardNo
ORDER BY boardGroupNo desc, step asc;
@id가 들어갈때마다 step역시 하나씩 증가해 들어가게 되기 때문에
출력 순서를 그대로 잡아주게 된다.
만약 함수를 사용하지 않고 step 컬럼을 만들어 처리한다면 데이터를 넣어줄때마다 수정을 해야하는 상황이 발생하지만 함수는 쿼리문에서 호출하면서 처리해주니까 그런 문제가 없어서 좀 더 낫다고 생각한다.
내림차순으로 조회하지 않는 경우는 step을 굳이 사용할 필요가 없겠지만
내림차순으로 조회해야 한다면 step을 사용하는것이 좋은것 같다.
물론 다른방법도 있겠지만 아직까지는 이 방법 말고는 딱히 생각이 안난다..................................
방법 2. 재귀
함수 이외의 방법으로 제일 많이 언급된 방법이 아마 재귀쿼리인것 같다.
WITH RECURSIVE board_CTE AS (
SELECT boardNo
, boardTitle
, boardUpperNo
, boardIndent
, CAST(boardNo AS CHAR(100)) lvl
, boardGroupNo
FROM board
WHERE boardUpperNo = 0
UNION ALL
SELECT b.boardNo
, b.boardTitle
, b.boardUpperNo
, b.boardIndent
, CONCAT(c.lvl, ',', b.boardNo) lvl
, b.boardGroupNo
FROM board b
INNER JOIN board_CTE c
ON b.boardUpperNo = c.boardNo
)
SELECT boardNo
, CONCAT(REPEAT(' ', boardIndent), '', boardTitle) AS boardTitle
, boardUpperNo
, boardIndent
, lvl
, boardGroupNo
FROM board_CTE
ORDER BY boardGroupNo desc, lvl;
이 쿼리문의 경우 시작이 recursive 바깥 select가 시작지점이다.
from board_CTE로 recursive에 들어가게 된다.
CTE(common table expression)은 해당 SQL문 내에서만 존재하는 일시적인 테이블(결과의 집합)을 말한다.
WITH는 CTE를 생성하는 문법이고 RECURSIVE CTE는 서브쿼리에서 스스로를 참조하는 CTE이다.
이 안에서는 UNION으로 구분된 2파트로 나누어 진다.
첫 SELECT는 최초 행을 반환하고 두번째 SELECT는 추가행을 반환한다.
그리고 두번째 SELECT문이 더이상 행을 생성하지 않을 때 재귀가 끝나게 된다.
그럼 여기서 첫 SELECT문은 답글을 제외한 모든 원글의 데이터가 된다.
두번째 SELECT문은 board와 CTE를 조인해 UpperNo = boardNo를 만족하는 데이터를 찾아옴으로써
답글의 원글을 찾게 된다.
참고 데이터로 보자면 76번글은 UpperNo로 75를 갖고 있다.
그럼 조인에서 b.boardUpperNo(75) = c.boardNo(75) 이렇게 되고
조회되는 데이터는
b.boardNo(76)
, b.boardTitle(RE: 제목 75)
, b.boardUpperNo(75)
, b.boardIndent(1)
, lvl(75,76)
, b.boardGroupNo(75)
이렇게 된다.
기존 데이터와 다른부분이 lvl뿐이다.
그럼 76의 하위글인 77번글은?
76의 lvl을 그대로 가져와 lvl(75,76,77)
이렇게 된다.
이렇게 모든 데이터에 대한 조회를 하게 되는데
원글의 경우 upperNo가 0으로 되어있기 때문에 따로 조인이 일어나지 않아 가져오는 데이터가 없게 되고
답글들만 조회해 참조하고 있는 데이터의 No와 합쳐 lvl 을 생성하게 되는 구조다.
그럼 데이터는 아래와 같이 나오게 된다.
lvl에서는 최상위인 원글부터 그 아래 답글까지 다 갖게 해서 이걸로 정렬하게 된다.
그래서 내림차순으로 정렬하는 경우는 ORDER BY boardGroupNo desc, lvl asc로 처리해주면 되고
오름차순의 경우 ORDER BY boardGroupNo, lvl로 처리해주면 된다.
Oracle에서 start with ~ connect by 를 사용해보고 난 뒤여서 그런지
이 두방법 다 복잡해보이고 효율도 좋아보이지 않는다..
그래도 이전에 했던 방법처럼 step 컬럼으로 순서 조정하면서 찾아다가 수정하고 등록하고 하는 방법보다는 나은것 같아서 이렇게 계층에 제한이 없는 조건이라면 이 방법이 더 나은것 같긴 하다.
하지만 요즘 사이트 댓글들 보면 그냥 한계층만 가능하도록 하는 경우도 많아서 그럴때는 또 굳이 이렇게까지는 쓰지 않아도 될것같다.
그때그때 알아서 잘 사용할 수 있게 미리 배워둔다는 느낌으로 알아두면 좋을듯!!!!
Reference
- stored routine(READS SQL DATA)
MySQL Functions 생성, Stored 루틴 및 트리거 바이너리 로깅
먼저 function 을 만들려는 데 다음과 같은 에러가 발생했다. ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you..
blog.pages.kr
- 함수 문법
[Mysql]Function 과 Procedure(함수와 프로시저) -1
안녕하세요. 오늘은 Function과 Procedure을 공부하겠습니다. 1편에서는 함수에 대해 잘 알아볼께요. Fu...
blog.naver.com
DETERMINISTIC , NOT DETERMINISTIC
함수나 프로시저 같은 Object 생성시 사용할 수 있는 옵션입니다. default값은 NOT DETERMINISTIC입니다. 아무것도 입력하지 않으면 자동으로 NOT DETERMINISTIC이 설정됩니다. 동일한 입력 매개 변수에 대해
bae9086.tistory.com
- WITH RECURSIVE
MySQL WITH RECURSIVE
CTE와 재귀적 CTE
velog.io
- 계층형 쿼리 참고
GNUJAVA
MySQL 에는 안타깝게도 Oracle 의 start with, connect by 를 지원하는 함수가 없다... 때문에 아래와 같이 function 을 만들어서 사용한다. 예제 테이블) test.servers_group create table test.servers_group ( group_idx in
www.gnujava.com
'DB' 카테고리의 다른 글
Query에서 if 조건 사용해 처리하기 (0) | 2023.09.27 |
---|---|
Oracle Scheduler, MySQL Event Scheduler (0) | 2022.04.21 |
EC2에 설치한 MySQL 데스크탑 workbench에서 접근 (0) | 2021.11.09 |
Oracle과 Tomcat충돌문제 해결 (0) | 2020.10.18 |
Oracle에서 auto_increment (0) | 2020.10.17 |