Как создать таблицу закрытия, используя данные из списка смежности?
У меня есть база данных, содержащая иерархию категорий, хранящихся с использованием модели списка смежности.
Иерархия имеет 3 уровня глубины (не включая воображаемый корень node) и содержит приблизительно 1700 узлов. Узлы второго и третьего уровней могут иметь несколько родителей. Дополнительная таблица используется для отношений "многие-ко-многим", как показано ниже:
CREATE TABLE dbo.Category(
id int IDENTITY(1,1) NOT NULL,
name varchar(255) NOT NULL,
)
CREATE TABLE dbo.CategoryHierarchy(
relId int IDENTITY(1,1) NOT NULL,
catId int NOT NULL,
parentId int NOT NULL,
)
Если я перейду к использованию метода транзитивной таблицы закрытия (ради целостности данных и т.д.), существует ли относительно простой запрос, который мог бы генерировать значения для таблицы закрытия? (с использованием SQL Server 2005)
Я просматриваю статьи и презентации, такие как Bill Karwin Модели для иерархических данных, но имеет только запросы на вставку для одного node и для меня потребовалось бы навсегда создать мое дерево.
Спасибо.
РЕДАКТИРОВАТЬ:
RelID в таблице CategoryHierarchy используется исключительно для первичного ключа, он не имеет отношения к идентификаторам node таблицы Category.
Кроме таблицы закрытия, я имею в виду таблицу, подобную этой:
CREATE TABLE ClosureTable (
ancestor int NOT NULL,
descendant int NOT NULL,
[length] int NOT NULL,
)
Если первые два столбца являются составным первичным ключом и являются отдельными внешними ключами для Category.id.
Ответы
Ответ 1
Я думаю, что сам смог решить проблему.
Если у кого-то есть лучший способ сделать это, прокомментируйте.
IF OBJECT_ID('dbo.ClosureTable', 'U') IS NOT NULL
DROP TABLE dbo.ClosureTable
GO
CREATE TABLE dbo.ClosureTable (
ancestor int NOT NULL,
descendant int NOT NULL,
distance int NULL
)
GO
DECLARE @depth INT
SET @depth = 1
INSERT INTO dbo.ClosureTable (ancestor, descendant, distance)
SELECT catid, catid, 0 FROM dbo.Category -- insert all the self-referencing nodes
WHILE (@depth < 4) -- my tree is only 4 levels deep, i.e 0 - 3
BEGIN
INSERT INTO dbo.ClosureTable (ancestor, descendant, distance)
SELECT ct.ancestor, h.catid, @depth
FROM dbo.ClosureTable ct INNER JOIN dbo.CategoryHierarchy h ON ct.descendant = h.parentid
WHERE ct.distance = @depth - 1
SET @depth = @depth + 1
END
Приветствия:)
Ответ 2
Я пытался выяснить то же самое, но хотел его в рекурсивном CTE. Это не сработало бы для вас (SQL Server 2008+), но вот то, что я закончил, для кого-то другого.
Трудно объяснить, как это работает, но ключ заключается в том, что якоря не являются вашими корневыми узлами (где parent_id IS NULL
), но вместо этого все ваши строки с нулевой глубиной находятся в таблице закрытия.
Таблица
CREATE TABLE dbo.category
(
id INT IDENTITY(1, 1) NOT NULL,
parent_id INT NULL
)
Данные
INSERT INTO dbo.category (id, parent_id)
VALUES
(1, NULL),
(2, 1),
(3, 1),
(4, 2)
CTE
WITH category_cte AS
(
SELECT
id AS ancestor,
id AS descendant,
0 AS depth
FROM dbo.category
UNION ALL
SELECT
CTE.ancestor AS ancestor,
C.id AS descendant,
CTE.depth + 1 AS depth
FROM dbo.category AS C
JOIN category_cte AS CTE
ON C.parent_id = CTE.descendant
)
SELECT * FROM category_cte
Результат
ancestor descendant depth
-------- ---------- -----
1 1 0
2 2 0
3 3 0
4 4 0
2 4 1
1 2 1
1 3 1
1 4 2
Ответ 3
WITH Children (id, name, iteration) AS (
SELECT id, name, 0
FROM Category
--WHERE id = @folderid -- if you want a startpoint
UNION ALL
SELECT b.id, b.name, a.iteration + 1
FROM Children AS a, Category AS b, CategoryHierarchy c
WHERE a.id = c.parentId
AND b.id = c.catId
)
SELECT id, name, iteration FROM Children
Не тестировалось, но должно работать так. По крайней мере, начинайте, как вы делаете это быстро без циклов.
EDIT: я пропустил, а не relId, это parentId, который должен ссылаться на таблицу Childrens. Но результатом должна быть таблица со всеми таблицами. Так что это не то, что вы изначально хотели?