1. 테이블 생성

 drop table test_table;

CREATE TABLE `TEST_TABLE` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`p_id` int(10) unsigned default '0',
`nm` varchar(50),
primary key(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 


2. 테이블 데이터 추가

 insert into TEST_TABLE(p_id, nm) values ( 0, '왕');

insert into TEST_TABLE(p_id, nm) values ( 1, '왕자');

insert into TEST_TABLE(p_id, nm) values ( 1, '공주');

insert into TEST_TABLE(p_id, nm) values ( 2, '왕자아들');

insert into TEST_TABLE(p_id, nm) values ( 2, '왕자딸');

insert into TEST_TABLE(p_id, nm) values ( 3, '공주아들');

insert into TEST_TABLE(p_id, nm) values ( 6, '공주아들의 딸');

insert into TEST_TABLE(p_id, nm) values ( 1, '배다른 형제');

 



3. Function 생성

 DROP FUNCTION IF EXISTS get_lvl_test_table;

DELIMITER $$
CREATE FUNCTION  get_lvl_test_table() RETURNS 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(id) INTO @id
          FROM TEST_TABLE
         WHERE p_id = v_parent
           AND id > v_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 id, p_id
          INTO v_id , v_parent
          FROM TEST_TABLE
         WHERE id = v_parent;
    END LOOP;
END
$$
DELIMITER ;

 



4. 실행

 SELECT
    CONCAT(REPEAT('  ', level  - 1), d.nm) AS nm
    ,d.id
    ,d.p_id
    ,func.level
FROM
    (SELECT
        get_lvl_TEST_TABLE() AS id, @level AS level
    FROM
        (SELECT @start_with:=0, @id:=@start_with, @level:=0) vars
    JOIN test_table
    WHERE
        @id IS NOT NULL) func
        JOIN
    TEST_TABLE d ON func.id = d.id;

 


==============================================================================================================
==============================================================================================================

출처 : http://www.lovelgw.com/Blog/275
         http://blog.naver.com/tyboss/70081682199


장점 : 무한 계층 가능
단점 : 구현이 어렵다.
 

테이블 설계 중에 계층적 구조를 설계할 필요가 있을때가 있습니다. 자료의 카테고리나 메뉴의 구성 또는 회원의 분류 또는 데이터의 계층적 구조를 표현을 할때 설계가 필요합니다. 자식이 부모를 가지는 형태에서 하위 자식 분류에 제어 및 추가 삭제가 편리한 방법을 알아 보도록 하겠습니다. MySQL Hierarchical Data 에서 소개안 방법을 설명 합니다.

Nested Set Model (중첩 설정 모델)
Nested Set Model 을 이용하여 계층적 구조를 표현해보도록 하겠습니다. 아래 그림은 보라색 화살표 방향으로 정렬이 됩니다. 모든 노드의 왼쪽 값을 가지고 정렬을 하고 있습니다.
Nested Set Model

그림 1 - Nested Set Model 정렬


위 그림은 부모 분류가 자식 분류를  포함하도록 설계가 했습니다. 1개의 분류에는 자식을 포함 할 수 있는 범위가 들어있습니다. 즉 1개의 자식을 추가하기 위해서는 해당 자식노드를 포함하는 상위 레벨의 모든 부모에 값을 변경해주어야 합니다. 그리고 탐색순서는 자식 노드의 범위 값 중 작은 범위의 값(Left point)를 가지고 정렬을 하게 됩니다.

반대로 자식 노드의 삭제의 경우에 해당 자식노드를 삭제 하고 포함한 부모와 자식을 포함하지 않은 그외 노드에 대한값도 삭제된 자식 노드의 범위만큼 범위를 수정해주어야 합니다.

위 방식으로 테이블을 만들어 보겠습니다.

 CREATE TABLE NestedSetMenu
(
nSeq INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
strName VARCHAR(30) NOT NULL,
nLeft INT(10) UNSIGNED NOT NULL,
nRight INT(10) UNSIGNED NOT NULL,
eStatus enum('NORMAL','WAIT','PTMP','NTMP') NOT NULL DEFAULT 'NORMAL'
PRIMARY KEY(`nSeq`)
)
ENGINE=MyISAM;

 

테이블을 생성하고 데이터를 넣습니다. 이후 삽입되는 데이터의 경우 부모의 값을 이용하여 하위로 들어가게 되고 구조상 동일 레벨의 가장 오른쪽(그림 1 표현에서)에 데이터가 삽입됩니다.

삽입 되는 과정 및 구조 설명입니다.
1. 선택한 부모의 우측포인트를 구합니다.
2. Node 가 들어갈 자리를 마련하기 위해 우측 값이 부모의 우측 값 보다 크거나 같은모든 값에 +2를 해줍니다.
3. Node 가 들어갈 자리를 마련하기 위해 좌측 값이 부모의 우측 값 보다 큰 모든 값을 + 2를 해줍니다.
4. 노드를 입력합니다. (좌 부모 우측값, 우 = 부모우측값 + 1)

삭제 되는 과정 및 구조 설명입니다.
1. 삭제할 노드의 왼쪽 , 오른쪽 값을 구합니다.
2. 삭제할 노드의 범위를 구합니다. ((오른쪽 - 왼쪽) + 1)
3. 노드를 삭제 합니다.
4. 노드의 오른쪽 범위와 왼쪽 범위보다 큰 값(왼쪽 ,오른쪽)들을 삭제 노드의 범위 만큼 빼줍니다.

노드가 이동되는 구조 설명입니다.
1. 옮겨질 노드의 왼쪽, 오른쪽 , 범위 값을 구합니다.
2. 옮겨질 노드의 값을 초기값 (최상단 왼쪽 노드의 좌측값을 1로 시작해 초기화 시킵니다.)
3. 옮겨질 노드의 상태값을 변경합니다.(그외 노드를 수정시 변경되는것을 방지합니다.)
4. 그외 노드의 Left, Right 값을 이동할 노드의 범위 만큼 빼줍니다.  (삭제와 동일)
5. 대상 노드의 정보를 구합니다.
6. 대상 노드에 옮겨질 노드가 들어올 자리를 마련하기 위해 Left, Right 값을 옮겨질 노드의 범위만큼 더해줍니다. (Right 가 대상 노드의 Right 와 같거나 큰 노드, Left 가 Right보다 큰 노드)
7. 옮겨질 노드를 대상 노드의 위치로 옮기기 위해 대상 노드의 Right 값에서 1을 뺀만큼 Left, Right 에 더해줍니다.

노드의 순위를 변경하는 구조 설명입니다.
1. 순서의 변경은 대상노드가 왼쪽에서 오른쪽으로 이동형식으로 이루어집니다. (노드의 우선순위 산정)
2. 왼쪽 노드의 정보를 구합니다.
3. 왼쪽 노드의 Left, Right 를 오른쪽 노드와 왼쪽노드의 거리(오른쪽 노드 Right - 왼쪽노드의 Right) 를 더해줍니다.
4. 그외 노드에서 Left 가 왼쪽노드의 Right, 오른쪽 노드의 Right 범위에 해당하는 노드들의 Left, Right 값을 이동할 노드의 범위 만큼 빼줍니다.

위 방식으로 노드의 추가, 삭제 , 이동, 순위 변경이 가능합니다. 시간이 나는대로 도표를 그려 첨부해야겟네요 ^^ 말로 하자니 조금 복잡한 면이 있어 보입니다 ^^
아래 프로시져는 위 노드의 계층형 구조를 보여주는 프로시져 입니다. ^^

DROP PROCEDURE IF EXISTS showNode;
DELIMITER %%
CREATE PROCEDURE showNode()
BEGIN
	SELECT
		Node.nSeq,
		CONCAT(REPEAT("\t", (COUNT(Parent.nSeq) - 1) ), Node.strName) as strName,
		Node.nLeft,
		Node.nRight
	FROM
		NestedSetMenu AS Node,
			NestedSetMenu AS Parent
	WHERE
		Node.nLeft BETWEEN Parent.nLeft AND Parent.nRight
	GROUP BY Node.nSeq
	ORDER By Node.nLeft;
END %%
DELIMITER ;

 

 

위 방식으로 카테고리, 메뉴등을 작업하면 메뉴의 순위 변경이나 부모 노드의 삭제로 인해 자식노드가 부모를 잃어버리는 경우를 피할 수 있습니다. 또한 계층형 출력이나 , 관리등에서 많은 잇점을 가지고 있습니다. (게시판등에서 이용하는것은 비효율적이겠죠? 업데이트시 상당한 부하가 걸릴테니까요 ^^)

노드의 변경에 따른 도표는 지금 작성중이어서 그림으로 차차 추가 하도록 하겠습니다.

부록 : 예제 테이블에서 사용할 수 있는 프로시져입니다. 한번 테스트 해보세요~ ^^ 실제 사용에는 약간의 기능 추가및 예외 처리가 필요합니다. 아래 예제 사용시 발생되는 문제는 책임지지 않습니다. Nested Set Model 을 설명하기 위해 예제로 작성해본 프로시져입니다 ^^

-- 계층 구조는 범위를 포함하게 되는 왼쪽 오른쪽 값과 -- 노드의 위치이동 및 변경시 필요한 상태값을 가지고 있다. CREATE TABLE NestedSetMenu ( nSeq INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, strName VARCHAR(30) NOT NULL, nLeft INT(10) UNSIGNED NOT NULL, nRight INT(10) UNSIGNED NOT NULL, eStatus enum('NORMAL','WAIT') NOT NULL DEFAULT 'NORMAL' PRIMARY KEY(`nSeq`) ) ENGINE=MyISAM; -- 저장을 위한 기본 구조와 프로시져 -- 메뉴 추가 프로시져 DROP PROCEDURE IF EXISTS addMenu; DELIMITER %% CREATE PROCEDURE addMenu(strMenuName CHAR(30), nParent INT) BEGIN DECLARE nNewRight INT; -- 선택한 부모의 우측 범위 값을 구한다. SELECT nRight INTO nNewRight FROM NestedSetMenu WHERE nSeq = nParent; -- Node 가 들어갈 자리를 마련하기 위해 들어갈 Node의 우측 범위를 포함하여 큰 값들에 + 2를 해준다. UPDATE NestedSetMenu SET nRight = nRight + 2 WHERE nRight >= nN UPDATE NestedSetMenu SET nLeft = nLeft + 2 WHERE nLeft > nNewRight; -- 실제 노드를 입력한다. INSERT INTO NestedSetMenu (nSeq, strName, nLeft, nRight, eStatus) VALUES (DEFAULT, strMenuName, IFNULL(nNewRight, 1), IFNULL(nNewRight + 1, 2), 'NORMAL'); END %% DELIMITER ; -- 노드 삭제 DROP PROCEDURE IF EXISTS deleteMenu; DELIMITER %% CREATE PROCEDURE deleteMenu(nSequence INT) BEGIN -- 삭제할 노드의 왼쪽, 오른쪽, 범위를 구한다. DECLARE nTargetLeft INT; DECLARE nTargetRight INT; DECLARE nTargetRange INT; -- 삭제할 노드의 범위를 구한다. SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nTargetLeft, nTargetRight, nTargetRange FROM NestedSetMenu WHERE nSeq = nSequence; -- 노드를 삭제한다.(자식을 포함하여 삭제) DELETE FROM NestedSetMenu WHERE nLeft BETWEEN nTargetLeft AND nTargetRight; -- 노드의 오른쪽 범위와 왼쪽 범위 보다 큰값들을 삭제 노드 범위 만큼 값을 빼준다. UPDATE NestedSetMenu SET nRight = nRight - nTargetRange WHERE nRight > nTargetRight; UPDATE NestedSetMenu SET nLeft = nLeft - nTargetRange WHERE nLeft > nTargetLeft; END %% DELIMITER ; -- 노드의 순서를 바꾼다.-- 노드 순위 변경 -- 버그 수정 -- 주의 점 : 항상 Node 는 좌측에서 우측으로 이동한다. -- 만약 우측이 좌측보다 left 값이 작을 경우 -- 판별을 해 좌우를 변경한 후 실행한다. DROP PROCEDURE IF EXISTS changeOrder; DELIMITER %% CREATE PROCEDURE changeOrder(nPrevSeq INT, nNextSeq INT) BEGIN DECLARE nPrevLeft INT; DECLARE nPrevRight INT; DECLARE nPrevWidth INT; DECLARE nNextLeft INT; DECLARE nNextRight INT; DECLARE nNextWidth INT; SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nPrevLeft, nPrevRight, nPrevWidth FROM NestedSetMenu WHERE nSeq = nPrevSeq; SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nNextLeft, nNextRight, nNextWidth FROM NestedSetMenu WHERE nSeq = nNextSeq; -- 이동할 노드의 left, right 를 (타겟 right - 이동할노드 right) 만큼 빼준다. UPDATE NestedSetMenu SET eStatus = 'WAIT' , nLeft = nLeft + (nNextRight - nPrevRight), nRight = nRight + (nNextRight - nPrevRight)
WHERE nLeft BETWEEN nPrevLeft AND nPrevRight; -- nLeft 가 이동할 노드의 right, 타겟 right 에 포함되는것의 left,right 를 이동할 노드의 range 만큰 빼준다. UPDATE NestedSetMenu SET nLeft = nLeft - nPrevWidth, nRight = nRight - nPrevWidth WHERE eStatus = 'NORMAL' AND nLeft BETWEEN nPrevRight AND nNextRight; -- status 값을 원래대로 변경한다. UPDATE NestedSetMenu SET eStatus = 'NORMAL' WHERE eStatus = 'WAIT'; END %% DELIMITER ; -- 노드의 이동 -- 버그 수정 상위 노드로 이동시 메뉴 구조가 깨지는 현상 수정 DROP PROCEDURE IF EXISTS moveNode; DELIMITER %% CREATE PROCEDURE moveNode(nSelectSeq INT, nTargetSeq INT) BEGIN DECLARE nSelectLeft INT; DECLARE nSelectRight INT; DECLARE nSelectWidth INT; DECLARE nTargetLeft INT; DECLARE nTargetRight INT; DECLARE nTargetWidth INT; SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nSelectLeft, nSelectRight, nSelectWidth FROM NestedSetMenu WHERE nSeq = nSelectSeq; -- 이동할 노드 초기화 UPDATE NestedSetMenu SET nLeft = nLeft - (nSelectLeft - 1), nRight = nRight - (nSelectLeft - 1), eStatus = 'WAIT' WHERE nLeft BETWEEN nSelectLeft AND nSelectRight; -- 이동할 노드 이외의 노드 Left, Right 값 변경 (선택된 노드 범위만큼 빼준다.) UPDATE NestedSetMenu SET nRight = nRight - nSelectWidth WHERE nRight > nSelectRight AND eStatus = 'NORMAL'; UPDATE NestedSetMenu SET nLeft = nLeft - nSelectWidth WHERE nLeft > nSelectRight AND eStatus = 'NORMAL'; -- 이동할 노드를 삭제한것과 동일 -- 대상 노드 정보 가져오기 SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nTargetLeft, nTargetRight, nTargetWidth FROM NestedSetMenu WHERE nSeq = nTargetSeq; -- 삭제한 노드를 타겟 노드 위치에 이동하기 위해 Left, Right 값을 변경한다. -- Right 값이 타겟의 Right 값보다 같거나 큰것들을 선택한 노드의 너비 만큼 더해준다. UPDATE NestedSetMenu SET nRight = nRight + nSelectWidth WHERE nRight >= nTargetRight AND eStatus = 'NORMAL'; -- Left 값이 타겟의 Right 값보다 큰 것들을 선택한 노드의 너비 만큼 더해준다. UPDATE NestedSetMenu SET nLeft = nLeft + nSelectWidth WHERE nLeft > nTargetRight AND eStatus = 'NORMAL'; -- 노드를 타겟 하위로 위치 시키기 위해 타겟의 (nTargetRight - 1) 값만큼 더해준다. UPDATE NestedSetMenu SET nLeft = nLeft + (nTargetRight - 1) , nRight = nRight + (nTargetRight - 1), eStatus = 'NORMAL' WHERE eStatus = 'WAIT'; END %% DELIMITER ; 

 


==============================================================================================================
==============================================================================================================

출처 : http://blog.daum.net/neolinux/64

장점 : 쉽다.
단점 : 무한 계층 불가

drop table category;

CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL,
ord int);

INSERT INTO category
VALUES
    (1,'게임',NULL, 1),
    (2,'두뇌게임/퍼즐',1,3),
    (3,'스포츠',1,2),
    (4,'아케이드',1,1),
    (5,'애플리케이션',null,2),
    (6,'교육',5,3),
    (7,'도구',5, 1),
    (8,'도서',5,2);


SELECT * FROM category ORDER BY category_id;

SELECT t1.name AS lev1, t2.name as lev2,
       concat(lpad(t1.ord, 3, 0), lpad(t2.ord, 3, 0)) ord
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
WHERE t1.parent is null
order by ord

 

 

-- 카테고리 테이블을 작성한다.

DROP TABLE IF EXISTS category ;
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);


INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);

SELECT * FROM category ORDER BY category_id;


-- self 조인을 이용한 DEPTH ( LEVEL ) 구현 쿼리
-- 일반적인 방법이다.
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

-- 레벨의 마지막 leaf node 만을 조회
 
SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;


-- 한개의 카테고리에 대해 상위 카테고리들 조회
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH';
 

출처 : http://tyboss.tistory.com/entry/MySQL-%EC%9E%AC%EA%B7%80%EC%BF%BC%EB%A6%AC-recursive

'IT > MySQL' 카테고리의 다른 글

백업 및 복원  (0) 2015.04.03
스케쥴 관련  (0) 2015.04.02
MySQL에서의 RowNum 사용  (0) 2015.02.13
임시테이블 사용 및 변수(DECLARE) 사용  (0) 2015.02.13
MySQL 수학 함수 정리  (0) 2015.02.11

+ Recent posts

1. 테이블 생성

 drop table test_table;

CREATE TABLE `TEST_TABLE` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`p_id` int(10) unsigned default '0',
`nm` varchar(50),
primary key(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 


2. 테이블 데이터 추가

 insert into TEST_TABLE(p_id, nm) values ( 0, '왕');

insert into TEST_TABLE(p_id, nm) values ( 1, '왕자');

insert into TEST_TABLE(p_id, nm) values ( 1, '공주');

insert into TEST_TABLE(p_id, nm) values ( 2, '왕자아들');

insert into TEST_TABLE(p_id, nm) values ( 2, '왕자딸');

insert into TEST_TABLE(p_id, nm) values ( 3, '공주아들');

insert into TEST_TABLE(p_id, nm) values ( 6, '공주아들의 딸');

insert into TEST_TABLE(p_id, nm) values ( 1, '배다른 형제');

 



3. Function 생성

 DROP FUNCTION IF EXISTS get_lvl_test_table;

DELIMITER $$
CREATE FUNCTION  get_lvl_test_table() RETURNS 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(id) INTO @id
          FROM TEST_TABLE
         WHERE p_id = v_parent
           AND id > v_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 id, p_id
          INTO v_id , v_parent
          FROM TEST_TABLE
         WHERE id = v_parent;
    END LOOP;
END
$$
DELIMITER ;

 



4. 실행

 SELECT
    CONCAT(REPEAT('  ', level  - 1), d.nm) AS nm
    ,d.id
    ,d.p_id
    ,func.level
FROM
    (SELECT
        get_lvl_TEST_TABLE() AS id, @level AS level
    FROM
        (SELECT @start_with:=0, @id:=@start_with, @level:=0) vars
    JOIN test_table
    WHERE
        @id IS NOT NULL) func
        JOIN
    TEST_TABLE d ON func.id = d.id;

 


==============================================================================================================
==============================================================================================================

출처 : http://www.lovelgw.com/Blog/275
         http://blog.naver.com/tyboss/70081682199


장점 : 무한 계층 가능
단점 : 구현이 어렵다.
 

테이블 설계 중에 계층적 구조를 설계할 필요가 있을때가 있습니다. 자료의 카테고리나 메뉴의 구성 또는 회원의 분류 또는 데이터의 계층적 구조를 표현을 할때 설계가 필요합니다. 자식이 부모를 가지는 형태에서 하위 자식 분류에 제어 및 추가 삭제가 편리한 방법을 알아 보도록 하겠습니다. MySQL Hierarchical Data 에서 소개안 방법을 설명 합니다.

Nested Set Model (중첩 설정 모델)
Nested Set Model 을 이용하여 계층적 구조를 표현해보도록 하겠습니다. 아래 그림은 보라색 화살표 방향으로 정렬이 됩니다. 모든 노드의 왼쪽 값을 가지고 정렬을 하고 있습니다.
Nested Set Model

그림 1 - Nested Set Model 정렬


위 그림은 부모 분류가 자식 분류를  포함하도록 설계가 했습니다. 1개의 분류에는 자식을 포함 할 수 있는 범위가 들어있습니다. 즉 1개의 자식을 추가하기 위해서는 해당 자식노드를 포함하는 상위 레벨의 모든 부모에 값을 변경해주어야 합니다. 그리고 탐색순서는 자식 노드의 범위 값 중 작은 범위의 값(Left point)를 가지고 정렬을 하게 됩니다.

반대로 자식 노드의 삭제의 경우에 해당 자식노드를 삭제 하고 포함한 부모와 자식을 포함하지 않은 그외 노드에 대한값도 삭제된 자식 노드의 범위만큼 범위를 수정해주어야 합니다.

위 방식으로 테이블을 만들어 보겠습니다.

 CREATE TABLE NestedSetMenu
(
nSeq INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
strName VARCHAR(30) NOT NULL,
nLeft INT(10) UNSIGNED NOT NULL,
nRight INT(10) UNSIGNED NOT NULL,
eStatus enum('NORMAL','WAIT','PTMP','NTMP') NOT NULL DEFAULT 'NORMAL'
PRIMARY KEY(`nSeq`)
)
ENGINE=MyISAM;

 

테이블을 생성하고 데이터를 넣습니다. 이후 삽입되는 데이터의 경우 부모의 값을 이용하여 하위로 들어가게 되고 구조상 동일 레벨의 가장 오른쪽(그림 1 표현에서)에 데이터가 삽입됩니다.

삽입 되는 과정 및 구조 설명입니다.
1. 선택한 부모의 우측포인트를 구합니다.
2. Node 가 들어갈 자리를 마련하기 위해 우측 값이 부모의 우측 값 보다 크거나 같은모든 값에 +2를 해줍니다.
3. Node 가 들어갈 자리를 마련하기 위해 좌측 값이 부모의 우측 값 보다 큰 모든 값을 + 2를 해줍니다.
4. 노드를 입력합니다. (좌 부모 우측값, 우 = 부모우측값 + 1)

삭제 되는 과정 및 구조 설명입니다.
1. 삭제할 노드의 왼쪽 , 오른쪽 값을 구합니다.
2. 삭제할 노드의 범위를 구합니다. ((오른쪽 - 왼쪽) + 1)
3. 노드를 삭제 합니다.
4. 노드의 오른쪽 범위와 왼쪽 범위보다 큰 값(왼쪽 ,오른쪽)들을 삭제 노드의 범위 만큼 빼줍니다.

노드가 이동되는 구조 설명입니다.
1. 옮겨질 노드의 왼쪽, 오른쪽 , 범위 값을 구합니다.
2. 옮겨질 노드의 값을 초기값 (최상단 왼쪽 노드의 좌측값을 1로 시작해 초기화 시킵니다.)
3. 옮겨질 노드의 상태값을 변경합니다.(그외 노드를 수정시 변경되는것을 방지합니다.)
4. 그외 노드의 Left, Right 값을 이동할 노드의 범위 만큼 빼줍니다.  (삭제와 동일)
5. 대상 노드의 정보를 구합니다.
6. 대상 노드에 옮겨질 노드가 들어올 자리를 마련하기 위해 Left, Right 값을 옮겨질 노드의 범위만큼 더해줍니다. (Right 가 대상 노드의 Right 와 같거나 큰 노드, Left 가 Right보다 큰 노드)
7. 옮겨질 노드를 대상 노드의 위치로 옮기기 위해 대상 노드의 Right 값에서 1을 뺀만큼 Left, Right 에 더해줍니다.

노드의 순위를 변경하는 구조 설명입니다.
1. 순서의 변경은 대상노드가 왼쪽에서 오른쪽으로 이동형식으로 이루어집니다. (노드의 우선순위 산정)
2. 왼쪽 노드의 정보를 구합니다.
3. 왼쪽 노드의 Left, Right 를 오른쪽 노드와 왼쪽노드의 거리(오른쪽 노드 Right - 왼쪽노드의 Right) 를 더해줍니다.
4. 그외 노드에서 Left 가 왼쪽노드의 Right, 오른쪽 노드의 Right 범위에 해당하는 노드들의 Left, Right 값을 이동할 노드의 범위 만큼 빼줍니다.

위 방식으로 노드의 추가, 삭제 , 이동, 순위 변경이 가능합니다. 시간이 나는대로 도표를 그려 첨부해야겟네요 ^^ 말로 하자니 조금 복잡한 면이 있어 보입니다 ^^
아래 프로시져는 위 노드의 계층형 구조를 보여주는 프로시져 입니다. ^^

DROP PROCEDURE IF EXISTS showNode;
DELIMITER %%
CREATE PROCEDURE showNode()
BEGIN
	SELECT
		Node.nSeq,
		CONCAT(REPEAT("\t", (COUNT(Parent.nSeq) - 1) ), Node.strName) as strName,
		Node.nLeft,
		Node.nRight
	FROM
		NestedSetMenu AS Node,
			NestedSetMenu AS Parent
	WHERE
		Node.nLeft BETWEEN Parent.nLeft AND Parent.nRight
	GROUP BY Node.nSeq
	ORDER By Node.nLeft;
END %%
DELIMITER ;

 

 

위 방식으로 카테고리, 메뉴등을 작업하면 메뉴의 순위 변경이나 부모 노드의 삭제로 인해 자식노드가 부모를 잃어버리는 경우를 피할 수 있습니다. 또한 계층형 출력이나 , 관리등에서 많은 잇점을 가지고 있습니다. (게시판등에서 이용하는것은 비효율적이겠죠? 업데이트시 상당한 부하가 걸릴테니까요 ^^)

노드의 변경에 따른 도표는 지금 작성중이어서 그림으로 차차 추가 하도록 하겠습니다.

부록 : 예제 테이블에서 사용할 수 있는 프로시져입니다. 한번 테스트 해보세요~ ^^ 실제 사용에는 약간의 기능 추가및 예외 처리가 필요합니다. 아래 예제 사용시 발생되는 문제는 책임지지 않습니다. Nested Set Model 을 설명하기 위해 예제로 작성해본 프로시져입니다 ^^

-- 계층 구조는 범위를 포함하게 되는 왼쪽 오른쪽 값과 -- 노드의 위치이동 및 변경시 필요한 상태값을 가지고 있다. CREATE TABLE NestedSetMenu ( nSeq INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, strName VARCHAR(30) NOT NULL, nLeft INT(10) UNSIGNED NOT NULL, nRight INT(10) UNSIGNED NOT NULL, eStatus enum('NORMAL','WAIT') NOT NULL DEFAULT 'NORMAL' PRIMARY KEY(`nSeq`) ) ENGINE=MyISAM; -- 저장을 위한 기본 구조와 프로시져 -- 메뉴 추가 프로시져 DROP PROCEDURE IF EXISTS addMenu; DELIMITER %% CREATE PROCEDURE addMenu(strMenuName CHAR(30), nParent INT) BEGIN DECLARE nNewRight INT; -- 선택한 부모의 우측 범위 값을 구한다. SELECT nRight INTO nNewRight FROM NestedSetMenu WHERE nSeq = nParent; -- Node 가 들어갈 자리를 마련하기 위해 들어갈 Node의 우측 범위를 포함하여 큰 값들에 + 2를 해준다. UPDATE NestedSetMenu SET nRight = nRight + 2 WHERE nRight >= nN UPDATE NestedSetMenu SET nLeft = nLeft + 2 WHERE nLeft > nNewRight; -- 실제 노드를 입력한다. INSERT INTO NestedSetMenu (nSeq, strName, nLeft, nRight, eStatus) VALUES (DEFAULT, strMenuName, IFNULL(nNewRight, 1), IFNULL(nNewRight + 1, 2), 'NORMAL'); END %% DELIMITER ; -- 노드 삭제 DROP PROCEDURE IF EXISTS deleteMenu; DELIMITER %% CREATE PROCEDURE deleteMenu(nSequence INT) BEGIN -- 삭제할 노드의 왼쪽, 오른쪽, 범위를 구한다. DECLARE nTargetLeft INT; DECLARE nTargetRight INT; DECLARE nTargetRange INT; -- 삭제할 노드의 범위를 구한다. SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nTargetLeft, nTargetRight, nTargetRange FROM NestedSetMenu WHERE nSeq = nSequence; -- 노드를 삭제한다.(자식을 포함하여 삭제) DELETE FROM NestedSetMenu WHERE nLeft BETWEEN nTargetLeft AND nTargetRight; -- 노드의 오른쪽 범위와 왼쪽 범위 보다 큰값들을 삭제 노드 범위 만큼 값을 빼준다. UPDATE NestedSetMenu SET nRight = nRight - nTargetRange WHERE nRight > nTargetRight; UPDATE NestedSetMenu SET nLeft = nLeft - nTargetRange WHERE nLeft > nTargetLeft; END %% DELIMITER ; -- 노드의 순서를 바꾼다.-- 노드 순위 변경 -- 버그 수정 -- 주의 점 : 항상 Node 는 좌측에서 우측으로 이동한다. -- 만약 우측이 좌측보다 left 값이 작을 경우 -- 판별을 해 좌우를 변경한 후 실행한다. DROP PROCEDURE IF EXISTS changeOrder; DELIMITER %% CREATE PROCEDURE changeOrder(nPrevSeq INT, nNextSeq INT) BEGIN DECLARE nPrevLeft INT; DECLARE nPrevRight INT; DECLARE nPrevWidth INT; DECLARE nNextLeft INT; DECLARE nNextRight INT; DECLARE nNextWidth INT; SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nPrevLeft, nPrevRight, nPrevWidth FROM NestedSetMenu WHERE nSeq = nPrevSeq; SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nNextLeft, nNextRight, nNextWidth FROM NestedSetMenu WHERE nSeq = nNextSeq; -- 이동할 노드의 left, right 를 (타겟 right - 이동할노드 right) 만큼 빼준다. UPDATE NestedSetMenu SET eStatus = 'WAIT' , nLeft = nLeft + (nNextRight - nPrevRight), nRight = nRight + (nNextRight - nPrevRight)
WHERE nLeft BETWEEN nPrevLeft AND nPrevRight; -- nLeft 가 이동할 노드의 right, 타겟 right 에 포함되는것의 left,right 를 이동할 노드의 range 만큰 빼준다. UPDATE NestedSetMenu SET nLeft = nLeft - nPrevWidth, nRight = nRight - nPrevWidth WHERE eStatus = 'NORMAL' AND nLeft BETWEEN nPrevRight AND nNextRight; -- status 값을 원래대로 변경한다. UPDATE NestedSetMenu SET eStatus = 'NORMAL' WHERE eStatus = 'WAIT'; END %% DELIMITER ; -- 노드의 이동 -- 버그 수정 상위 노드로 이동시 메뉴 구조가 깨지는 현상 수정 DROP PROCEDURE IF EXISTS moveNode; DELIMITER %% CREATE PROCEDURE moveNode(nSelectSeq INT, nTargetSeq INT) BEGIN DECLARE nSelectLeft INT; DECLARE nSelectRight INT; DECLARE nSelectWidth INT; DECLARE nTargetLeft INT; DECLARE nTargetRight INT; DECLARE nTargetWidth INT; SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nSelectLeft, nSelectRight, nSelectWidth FROM NestedSetMenu WHERE nSeq = nSelectSeq; -- 이동할 노드 초기화 UPDATE NestedSetMenu SET nLeft = nLeft - (nSelectLeft - 1), nRight = nRight - (nSelectLeft - 1), eStatus = 'WAIT' WHERE nLeft BETWEEN nSelectLeft AND nSelectRight; -- 이동할 노드 이외의 노드 Left, Right 값 변경 (선택된 노드 범위만큼 빼준다.) UPDATE NestedSetMenu SET nRight = nRight - nSelectWidth WHERE nRight > nSelectRight AND eStatus = 'NORMAL'; UPDATE NestedSetMenu SET nLeft = nLeft - nSelectWidth WHERE nLeft > nSelectRight AND eStatus = 'NORMAL'; -- 이동할 노드를 삭제한것과 동일 -- 대상 노드 정보 가져오기 SELECT nLeft, nRight, (nRight - nLeft + 1) INTO nTargetLeft, nTargetRight, nTargetWidth FROM NestedSetMenu WHERE nSeq = nTargetSeq; -- 삭제한 노드를 타겟 노드 위치에 이동하기 위해 Left, Right 값을 변경한다. -- Right 값이 타겟의 Right 값보다 같거나 큰것들을 선택한 노드의 너비 만큼 더해준다. UPDATE NestedSetMenu SET nRight = nRight + nSelectWidth WHERE nRight >= nTargetRight AND eStatus = 'NORMAL'; -- Left 값이 타겟의 Right 값보다 큰 것들을 선택한 노드의 너비 만큼 더해준다. UPDATE NestedSetMenu SET nLeft = nLeft + nSelectWidth WHERE nLeft > nTargetRight AND eStatus = 'NORMAL'; -- 노드를 타겟 하위로 위치 시키기 위해 타겟의 (nTargetRight - 1) 값만큼 더해준다. UPDATE NestedSetMenu SET nLeft = nLeft + (nTargetRight - 1) , nRight = nRight + (nTargetRight - 1), eStatus = 'NORMAL' WHERE eStatus = 'WAIT'; END %% DELIMITER ; 

 


==============================================================================================================
==============================================================================================================

출처 : http://blog.daum.net/neolinux/64

장점 : 쉽다.
단점 : 무한 계층 불가

drop table category;

CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL,
ord int);

INSERT INTO category
VALUES
    (1,'게임',NULL, 1),
    (2,'두뇌게임/퍼즐',1,3),
    (3,'스포츠',1,2),
    (4,'아케이드',1,1),
    (5,'애플리케이션',null,2),
    (6,'교육',5,3),
    (7,'도구',5, 1),
    (8,'도서',5,2);


SELECT * FROM category ORDER BY category_id;

SELECT t1.name AS lev1, t2.name as lev2,
       concat(lpad(t1.ord, 3, 0), lpad(t2.ord, 3, 0)) ord
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
WHERE t1.parent is null
order by ord

 

 

-- 카테고리 테이블을 작성한다.

DROP TABLE IF EXISTS category ;
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);


INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);

SELECT * FROM category ORDER BY category_id;


-- self 조인을 이용한 DEPTH ( LEVEL ) 구현 쿼리
-- 일반적인 방법이다.
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

-- 레벨의 마지막 leaf node 만을 조회
 
SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;


-- 한개의 카테고리에 대해 상위 카테고리들 조회
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS' AND t4.name = 'FLASH';
 

출처 : http://tyboss.tistory.com/entry/MySQL-%EC%9E%AC%EA%B7%80%EC%BF%BC%EB%A6%AC-recursive

'IT > MySQL' 카테고리의 다른 글

백업 및 복원  (0) 2015.04.03
스케쥴 관련  (0) 2015.04.02
MySQL에서의 RowNum 사용  (0) 2015.02.13
임시테이블 사용 및 변수(DECLARE) 사용  (0) 2015.02.13
MySQL 수학 함수 정리  (0) 2015.02.11

+ Recent posts