이제 MySQL 8.0은 재귀 쿼리를 지원하므로 모든 인기있는 SQL 데이터베이스 는 표준 구문으로 재귀 쿼리 를 지원 한다고 말할 수 있습니다 .
WITH RECURSIVE MyTree AS (
SELECT * FROM MyTable WHERE ParentId IS NULL
UNION ALL
SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;
2017의 Recursive Query Throwdown 프레젠테이션에서 MySQL 8.0의 재귀 쿼리를 테스트했습니다 .
아래는 2008 년의 원래 답변입니다.
트리 구조 데이터를 관계형 데이터베이스에 저장하는 방법에는 여러 가지가 있습니다. 예제에서 보여주는 것은 두 가지 방법을 사용합니다.
- 인접 목록 ( "부모"열) 및
- 경로 열거 (이름 열의 점으로 구분 된 숫자)
다른 솔루션을 Nested Sets 라고 하며 같은 테이블에 저장할 수도 있습니다. 이러한 디자인에 대한 자세한 내용은 Joe Celko의 " SQL for Smarties의 트리 및 계층 구조 "를 읽으십시오 .
저는 일반적으로 트리 구조 데이터를 저장하기 위해 클로저 테이블 (일명 "인접 관계") 이라는 디자인을 선호합니다 . 다른 테이블이 필요하지만 트리 쿼리는 매우 쉽습니다.
SQL 및 PHP를 사용한 계층 적 데이터 모델 프레젠테이션 및 저서 SQL 안티 패턴 : 데이터베이스 프로그래밍의 함정 피하기 에서 클로저 테이블을 다룹니다 .
CREATE TABLE ClosureTable (
ancestor_id INT NOT NULL REFERENCES FlatTable(id),
descendant_id INT NOT NULL REFERENCES FlatTable(id),
PRIMARY KEY (ancestor_id, descendant_id)
);
한 노드에서 다른 노드로 직접 조상이있는 클로저 테이블에 모든 경로를 저장하십시오. 각 노드가 자신을 참조하도록 행을 포함 시키십시오. 예를 들어 질문에 표시 한 데이터 세트를 사용하는 경우 :
INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
(1,1), (1,2), (1,4), (1,6),
(2,2), (2,4),
(3,3), (3,5),
(4,4),
(5,5),
(6,6);
이제 다음과 같이 노드 1에서 시작하는 트리를 얻을 수 있습니다.
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;
출력 (MySQL 클라이언트의 경우)은 다음과 같습니다.
+----+
| id |
+----+
| 1 |
| 2 |
| 4 |
| 6 |
+----+
다시 말해, 노드 3과 5는 노드 1에서 내려 가지 않고 별도의 계층 구조의 일부이므로 제외됩니다.
다시 : 직계 자녀 (또는 직계 부모)에 대한 전자 불만의 의견. " path_length
"열을에 추가 ClosureTable
하면 직계 자녀 나 부모 (또는 다른 거리)에 대해보다 쉽게 쿼리 할 수 있습니다.
INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
(1,1,0), (1,2,1), (1,4,2), (1,6,1),
(2,2,0), (2,4,1),
(3,3,0), (3,5,1),
(4,4,0),
(5,5,0),
(6,6,0);
그런 다음 주어진 노드의 직계 자식을 쿼리하기 위해 검색어에 용어를 추가 할 수 있습니다. 이들은 path_length
1의 자손입니다 .
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
AND path_length = 1;
+----+
| id |
+----+
| 2 |
| 6 |
+----+
@ashraf의 코멘트 : "전체 트리를 [이름별로] 정렬하는 것은 어떻습니까?"
다음은 노드 1의 자손 인 모든 노드를 반환하고와 같은 다른 노드 속성이 포함 된 FlatTable에 조인하고 name
이름을 기준으로 정렬하는 쿼리 예제 입니다.
SELECT f.name
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;
@Nate의 의견 :
SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id)
WHERE a.ancestor_id = 1
GROUP BY a.descendant_id
ORDER BY f.name
+------------+-------------+
| name | breadcrumbs |
+------------+-------------+
| Node 1 | 1 |
| Node 1.1 | 1,2 |
| Node 1.1.1 | 1,2,4 |
| Node 1.2 | 1,6 |
+------------+-------------+
사용자가 오늘 수정을 제안했습니다. SO 중재자가 편집을 승인했지만 되돌리고 있습니다.
편집 ORDER BY b.path_length, f.name
은 순서가 계층과 일치하는지 확인하기 위해 위의 마지막 쿼리에서 ORDER BY가되어야한다고 제안했습니다 . 그러나 "노드 1.2"다음에 "노드 1.1.1"을 주문하므로 작동하지 않습니다.
순서가 합리적인 방식으로 계층 구조와 일치하도록하려면 경로 길이를 기준으로 정렬하는 것이 아니라 가능합니다. 예를 들어 MySQL Closure Table 계층 데이터베이스에 대한 나의 답변 -올바른 순서로 정보를 가져 오는 방법을 참조하십시오 .