среда, 17 ноября 2010 г.

Задачная задача

Над этой задачей я бился 3 дня :-[
Есть таблица, содержащая дерево меню:

   create table menu( id     int primary key,  -- ID пункта меню
                      parent int null,         -- ID "родителя" (null для пунктов верхнего уровня)
                      name   char(30) )        -- Наименование пункта меню

Разработать сохраненную процедуру, позволяющую получить информацию о данном меню в следующем виде:

   Level    ID     Name
   ------   ----   ------------------------------------
   0        1      Меню 1
   1        5      Меню 1.1
   1        6      Меню 1.2
   2        10     Меню 1.2.1
   0        2      Меню 2
   0        3      Меню 3
   1        8      Меню 3.1
   2        12     Меню 3.1.1
   2        13     Меню 3.1.2
   1        9      Меню 3.2
   ...

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

Решение основано на создании суррогатного ключа, представляющего собой конкатенацию (склейку) строковых представлений ключей записей, являющихся "предками" данной записи
Например, для приведенной выше выборки данных ключи будут следующими:

synt        id           name                           
----------------------------------
1             1            Меню 1                         
15           5            Меню 1.1                       
16           6            Меню 1.2                       
1610       10          Меню 1.2.1                     
161011   11          Меню 1.2.1.1                   
2             2            Меню 2                         
3             3            Меню 3                         
38           8            Меню 3.1                       
3812       12          Меню 3.1.1                     
3813       13          Меню 3.1.2                     
39           9            Меню 3.2 

Фактически, задача представляет из себя поиск в глубину по дереву. Основная трудность заключалась в том, чтобы решить задачу, не используя рекурсии.

Итак, поехали:

CREATE PROCEDURE MyProc
AS
/* Первым делом создаем временную таблицу, включающую в себя поля
исходной таблицы плюс синтетический ключ и число уровней вложенности*/
CREATE TABLE #temp
(id int NULL,
parent int NULL,
synt varchar(255),
name char(30),
lev int
)

/*
Инициализируем таблицу значениями из имеющейся таблицы menu.
Синтетический ключ изначально представляет собой строковое представление первичного ключа.
*/
INSERT INTO #temp
SELECT id, parent, convert(varchar, id) AS synt, name, 0
FROM menu

WHILE ((SELECT COUNT(parent) FROM #temp) > 0)
BEGIN
/* "Переходим" на родительскую запись */
    UPDATE #temp
        SET id = parent
/* Изменяем указатель родительской записи */
    UPDATE #temp
        SET #temp.parent = (SELECT parent FROM menu WHERE menu.id = #temp.parent)
/* Добавляем идентификатор текущей записи к синтетическому ключу */
    UPDATE #temp
        SET synt = convert(varchar,id) + synt
/* Если мы еще не добрались до конца, увеличиваем уровень вложенности */
    UPDATE #temp
        SET lev = lev + 1
    WHERE (id IS NOT NULL)
END
/* Собственно, выборка данных */
SELECT #temp.lev,menu.id,menu.name
FROM menu, #temp
WHERE menu.name = #temp.name
ORDER BY synt
/* Не забываем убирать за собой! */
DROP TABLE #temp

Комментариев нет:

Отправить комментарий