Как создать таблицу закрытия, используя данные из списка смежности?

У меня есть база данных, содержащая иерархию категорий, хранящихся с использованием модели списка смежности.

Иерархия имеет 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. Но результатом должна быть таблица со всеми таблицами. Так что это не то, что вы изначально хотели?