Сгладить Иерархию Списка Смежности До Списка Всех Путей


У меня есть таблица, которая хранит иерархическую информацию, используя модель списка смежности. (использует самореферентный ключ-пример ниже. Эта таблица может выглядеть знакомо):

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 представляет текущий node и столбцы " lvl " являются его предками. Значение для текущего узла также должно находиться в самом дальнем правом столбце lvl. Значение в столбце lvl1 всегда будет представлять корневой узел, значения в столбце lvl2 всегда будут представлять прямые потомки lvl1 и т. д.

Если возможно, метод для генерации этого вывода будет в SQL и будет работать для n-уровневых иерархий.

4 8

4 ответа:

Для выполнения многоуровневых запросов по простому списку смежности неизменно требуется самосоединение слева. Легко сделать таблицу, выровненную по правому краю:

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-уровневых иерархий.

Извините, запросы произвольной глубины невозможны в модели списка смежности. Если вы часто выполняете такие запросы, вам следует изменить схему на одну из других моделей хранения иерархической информации : полное отношение смежности (хранение всех отношений предок-потомок), материализованный путь или вложенные множества.

Если категории не перемещаются много (что обычно имеет место для магазина, подобного вашему примеру), я бы склонялся к вложенным наборам.

Как уже упоминалось, SQL не имеет чистого способа реализации таблиц с динамически изменяющимся числом столбцов. Единственные два решения, которые я использовал раньше:: 1. Фиксированное число Самосоединяется, давая фиксированное число столбцов (согласно BobInce) 2. Сгенерируйте результаты в виде строки в одном столбце

Второй изначально звучит гротескно; хранение идентификаторов в виде строки?! Но когда выходные данные форматируются как XML или что-то еще, люди, кажется, не так сильно возражают.

Равным образом, это имеет очень большое значение. мало пользы, если вы затем хотите присоединиться к результатам в SQL. Если результат должен быть предоставлен приложению, он может быть очень подходящим. Лично, однако, я предпочитаю делать выравнивание в приложении, а не SQL


Я застрял здесь на 10-дюймовом экране без доступа к SQL, поэтому я не могу дать тестируемый код, но основной метод будет использовать рекурсию каким-то образом;
- Рекурсивная скалярная функция может сделать это
- MS SQL может сделать это с помощью рекурсивного с утверждение (более эффективное)

Скалярная функция (что-то вроде):

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


EDIT-вывод для обоих одинаков:

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'

Обход дерева произвольной глубины обычно включает рекурсивный процедурный код, если только вы не используете специальные возможности некоторых СУБД.

В Oracle предложение CONNECT BY позволит вам пройти по дереву в глубину первого порядка, если вы используете список смежности, как это было сделано здесь.

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

Фактически может быть сделано с помощью динамического SQL в рамках процедуры stores. Затем вы становитесь ограничены тем, что может быть сделано с помощью хранимой процедуры. Очевидно, что это становится проблемой, чтобы выполнить результаты во временную таблицу, не зная, сколько столбцов ожидать. Однако, если цель состоит в том, чтобы вывести на веб-страницу или другой пользовательский интерфейс, то это может стоить усилий...