인접성 목록 계층 구조를 모든 경로의 목록으로 평평하게
-
11-09-2019 - |
문제
인접성 목록 모델을 사용하여 계층 정보를 저장하는 테이블이 있습니다. (자체 참조 키 사용 - 아래 예제.이 테이블이 보일 수 있습니다. 친숙한):
category_id name parent
----------- -------------------- -----------
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
위의 데이터를 이와 같은 것으로 "평평하게"하는 가장 좋은 방법은 무엇입니까?
category_id lvl1 lvl2 lvl3 lvl4
----------- ----------- ----------- ----------- -----------
1 1 NULL NULL NULL
2 1 2 NULL NULL
6 1 6 NULL NULL
3 1 2 3 NULL
4 1 2 4 NULL
5 1 2 5 NULL
7 1 6 7 NULL
9 1 6 9 NULL
10 1 6 10 NULL
8 1 6 7 8
각 행은 행이있는 경우를 제외하고 계층 구조를 통한 하나의 "경로"입니다. 각 노드 (각각뿐만 아니라 잎 노드). Category_id 열은 현재 노드를 나타내고 "LVL"열은 조상입니다. 현재 노드의 값은 가장 먼 LVL 열에 있어야합니다. LVL1 열의 값은 항상 루트 노드를 나타내고 LVL2의 값은 항상 LVL1의 직접 후손 등을 나타냅니다.
가능하다면이 출력을 생성하는 방법은 SQL에 있으며 N-Tier 계층에 대해 작동합니다.
해결책
간단한 인접거 목록에서 멀티 레벨 쿼리를 수행하려면 항상 자기 적을 조절하는 것과 관련이 있습니다. 오른쪽 정렬 테이블을 쉽게 만들 수 있습니다.
SELECT category.category_id,
ancestor4.category_id AS lvl4,
ancestor3.category_id AS lvl3,
ancestor2.category_id AS lvl2,
ancestor1.category_id AS lvl1
FROM categories AS category
LEFT JOIN categories AS ancestor1 ON ancestor1.category_id=category.category_id
LEFT JOIN categories AS ancestor2 ON ancestor2.category_id=ancestor1.parent
LEFT JOIN categories AS ancestor3 ON ancestor3.category_id=ancestor2.parent
LEFT JOIN categories AS ancestor4 ON ancestor4.category_id=ancestor3.parent;
예제처럼 왼쪽에 정렬하는 것은 조금 더 까다 롭습니다. 이것은 마음에옵니다 :
SELECT category.category_id,
ancestor1.category_id AS lvl1,
ancestor2.category_id AS lvl2,
ancestor3.category_id AS lvl3,
ancestor4.category_id AS lvl4
FROM categories AS category
LEFT JOIN categories AS ancestor1 ON ancestor1.parent IS NULL
LEFT JOIN categories AS ancestor2 ON ancestor1.category_id<>category.category_id AND ancestor2.parent=ancestor1.category_id
LEFT JOIN categories AS ancestor3 ON ancestor2.category_id<>category.category_id AND ancestor3.parent=ancestor2.category_id
LEFT JOIN categories AS ancestor4 ON ancestor3.category_id<>category.category_id AND ancestor4.parent=ancestor3.category_id
WHERE
ancestor1.category_id=category.category_id OR
ancestor2.category_id=category.category_id OR
ancestor3.category_id=category.category_id OR
ancestor4.category_id=category.category_id;
N-Tier 계층 구조에 대해 작동합니다.
죄송합니다. 인접성 목록 모델에서는 임의의 심한 쿼리가 불가능합니다. 이런 종류의 쿼리를 많이하고 있다면 스키마를 하나로 변경해야합니다. 계층 적 정보 저장의 다른 모델: 완전한 인접 관계 (모든 조상-공인 관계 저장), 구체화 된 경로 또는 중첩 된 세트.
카테고리가 많이 움직이지 않으면 (일반적으로 예와 같은 상점의 경우) 중첩 된 세트를 향한 경향이 있습니다.
다른 팁
언급했듯이 SQL은 동적으로 다양한 수의 열을 가진 테이블을 구현할 수있는 깨끗한 방법이 없습니다. 내가 이전에 사용한 유일한 두 가지 솔루션은 다음과 같습니다. 1. 고정 번호 자체 조인, 고정 된 수의 열을 제공합니다 (Bobince에 따라) 2. 단일 열에서 문자열로 결과를 생성합니다.
두 번째는 처음에는 괴상한 소리를냅니다. ID를 문자열로 저장합니까?! 그러나 출력이 XML 또는 무언가로 형식화되면 사람들은 그렇게 많이 신경 쓰지 않는 것 같습니다.
마찬가지로, 이것은 SQL에서 결과에 참여하려면 거의 사용하지 않습니다. 결과를 응용 프로그램에 제공하려면 매우 적합 할 수 있습니다. 그러나 개인적으로, 나는 SQL이 아닌 응용 프로그램에서 아파트를하는 것을 선호합니다.
SQL에 액세스하지 않고 10 인치 화면에 붙어있어 테스트 된 코드를 제공 할 수는 없지만 기본 방법은 재귀를 어떤 식 으로든 활용하는 것입니다.
- 재귀 스칼라 함수가이를 수행 할 수 있습니다
-MS SQL은 Recursive with Statement (보다 효율적)를 사용하여이를 수행 할 수 있습니다.
스칼라 함수 (같은) :
CREATE FUNCTION getGraphWalk(@child_id INT)
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE @graph VARCHAR(4000)
-- This step assumes each child only has one parent
SELECT
@graph = dbo.getGraphWalk(parent_id)
FROM
mapping_table
WHERE
category_id = @child_id
AND parent_id IS NOT NULL
IF (@graph IS NULL)
SET @graph = CAST(@child_id AS VARCHAR(16))
ELSE
SET @graph = @graph + ',' + CAST(@child_id AS VARCHAR(16))
RETURN @graph
END
SELECT
category_id AS [category_id],
dbo.getGraphWalk(category_id) AS [graph_path]
FROM
mapping_table
ORDER BY
category_id
나는 한동안 재귀를 사용하지 않았지만, 여기에 SQL이 없어도 구문을 줄 것입니다. :)
재귀
WITH
result (
category_id,
graph_path
)
AS
(
SELECT
category_id,
CAST(category_id AS VARCHAR(4000))
FROM
mapping_table
WHERE
parent_id IS NULL
UNION ALL
SELECT
mapping_table.category_id,
CAST(result.graph_path + ',' + CAST(mapping_table.category_id AS VARCHAR(16)) AS VARCHAR(4000))
FROM
result
INNER JOIN
mapping_table
ON result.category_id = mapping_table.parent_id
)
SELECT
*
FROM
result
ORDER BY
category_id
편집 - 두 가지 모두에 대한 출력이 동일합니다.
1 '1'
2 '1,2'
3 '1,2,3'
4 '1,2,4'
5 '1,2,5'
6 '1,6'
7 '1,6,7'
8 '1,6,7,8'
9 '1,6,9'
임의의 깊이의 트리를 가로 지르면 일부 DBM의 특수 기능을 사용하지 않는 한 일반적으로 재귀적인 절차 코드가 포함됩니다.
Oracle에서 Connect By Clause를 사용하면 여기에서와 같이 인접력 목록을 사용하는 경우 트리를 먼저 순서대로 이동할 수 있습니다.
중첩 세트를 사용하는 경우 왼쪽 시퀀스 번호는 노드를 방문하라는 순서를 제공합니다.
실제로 상점 절차 내에서 동적 SQL로 수행 할 수 있습니다. 그런 다음 저장 절차를 할 수있는 일로 제한됩니다. 분명히 결과를 예상 수를 알지 못하는 임시 테이블로 결과를 실행하는 것은 어려운 일이됩니다. 그러나 목표가 웹 페이지 나 다른 UI에 출력하는 것이라면 노력할 가치가있을 수 있습니다 ...