Часто нам приходиться сталкиваться с проблемой хранения деревьев в базе, например, дерево категорий товаров в типичном приложении Интернет-магазина. Наверное самый простой способ – это хранить данные о родителе. Самый удобный, на мой взгляд, с точки зрения добавления, редактирования и удаления. Но не самый удобный для построения.

Один из вариантов деревьев, которые мне понравились в использовании – это деревья с левым и правым индексом – советую их использовать при больших объёмах данных, т.к. такие операции, как выборка поддерева, построения дерева линейны и не требуют дополнительного кода. Про эти деревья можно почитать здесь.

Я решил не мучаться, и использовать доработанный первый вариант – сохранять ещё и уровень вложенности.

Таким образом, в базе храняться поля id, parent_id, level, name (parent_id=0 – это корень дерева)

Что надо для выборки и построения дерева?
1. Выбрать записи из базы, предварительно отсортировать по level и parent_id

   $rows = $sql->fetchRowsById('SELECT *
FROM categories
ORDER BY level,parent_id,name');
// в результате мы имеем ассоциативный массив с id категории в роли ключа 

2. Дальше подготавливаем его для сортировки

   $currlvl = 0;
$counter = 0;
foreach ($rows as $id => $category) {
if ($category['level'] > $currlvl) {
$counter = 0;
$currlvl++;
}
$pre = '';
$parent_id = $category['parent_id'];
if (isset($rows[$parent_id])) {
$pre = $rows[$parent_id]['_sort_v'];
}
$rows[$id]['_sort_v'] = $pre.sprintf("%03x", $counter);
// вносим служебное поле, своего рода порядковый номер $counter++;
//данный ход сделан для того, чтобы сортировать, используя строковые функции
//тут используется ограничение на 1024 вложенных категорий, в принципе можно расширить и до 65К,
}
uasort($rows, create_function('$a, $b','return strcmp($a[_sort_v],$b[_sort_v]);'));
//собственно сортировка, в результате которой мы получаем дерево вложенности

Подводные камни - целостность базы. Так же нужен дополнительный код для операций добавления, перемещения и удаления узлов. В целом метод себя успешно зарекомендовал и используется, и нравиться тем, что время линейно (не считая работы sql и uasort.

P.S. данный метод хранения удобен так же при работе с Tree компонентами, т.к. на каждой итерации добавления узла родитель уже присутствует в дереве.

А каким образом вы храните и строите деревья?

google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru
          0 голосов

14 Комментариев на “Один из методов построения дерева”

  1. Rayan сказал:

    А каким образом вы храните и строите деревья?

    А мы с оными неработаем. Неприходилось

  2. Руслан Пилин сказал:

    В современных движках баз данных давно придумали такое понятие, как иерархический запрос. Именно для таких вот случаев работы с деревьями. В дибиту, например, эта проблема решаеться очень элегантно. Там есть уникальная поддержка рекурсивных запросов.

  3. GLad сказал:

    рекурсивный запрос сосёт по сравнению с лево-правыми деревьями. но если учитывать факт кеширования - тогда да, не спорю

  4. seagull сказал:

    Большое спасибо Гледу за возвращение к программерским темам. А то я боялся что Райан скоро начнет о городской политике тут писать :)

    На счет вопроса "как хранить деревья?" - некорректный вопрос. Надо спрашивать "как хранить деревья если критическая операция така-то". Потому что если важней всего скорость поиска, то лево-правые деревья не подходят однозначно :)

    Теперь важный момент - вставка кода в посты. Сейчас это очень нехорошо выглядит :( Сделайте что-нибудь, пожалуйста.
    Для подсветки можно использовать:
    http://softwaremaniacs.org/soft/highlight/
    Ну и pre автоматически подставлять, а то отступы слетают страшно.

  5. GLad сказал:

    Да мля, может нах выкинуть этот вордпресс :)
    хотя надо заэксплорить тему, или убрать этот визивиг нафик

  6. Rayan сказал:

    визвиг это что?
    какая альтернатива вордпресу?

    highlight я попробую интергрировать

  7. GLad сказал:

    визивиг редактор текстов в вордпрессе

  8. Fritz сказал:

    Для простых задач рекурсией

    Для больших статических объемов (географические обьекты классика страна-штат-графство-город-район) вложенными множествами - рекурсия приводит к жестким тупнякам(сервер MySQL:))

    Классов реализующих работу с NestedSets вообщето достаточно думаю для всех языков.

  9. zeroreturn сказал:

    В случае, если у категории может быть только один родитель, мне хватает одной таблицы, ссылающейся сама на себя.

    CREATE TABLE "types" (
    "id" SERIAL,
    "refParent" INTEGER,
    "name" VARCHAR(255) NOT NULL,
    CONSTRAINT "types_pkey" PRIMARY KEY("id"),
    CONSTRAINT "fkeyParentId" FOREIGN KEY ("refParent")
    REFERENCES "types"("id")
    ON DELETE CASCADE
    ON UPDATE CASCADE
    NOT DEFERRABLE
    ) WITHOUT OIDS;

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

    select name
    from "types"
    where "types"."refParent" = (
    select id from "types" where name='NODE_NAME'
    );

    Насчет хранить уровень идея хорошая, при вставке узла в середину дерева каскадом надо будет декрементировать значения уровня подлежащих узлов, а при удалении -- инкрементировать.

    Групповые операции по такому дереву не проводил. Хотя в один запрос вместить такое тяжело.

    Про множества почитал, но мне кажеться можно сделать как-то проще, слишком уж громоздкое решение.

  10. usix сказал:

    А почему бы не отказаться от реляционной модели в пользу иерархического хранилища?

  11. Руслан Пилин сказал:

    Это чего например? LDAP что ли?

  12. Fritz сказал:

    Ну это редко главная задача.А при нормальном инструментарии рутина и сложности прячутся.

  13. usix сказал:

    2 Руслан Пилин
    например файловая система.

  14. Виктор сказал:

    Спасибо было полезно, узнал много нового





Оставте свое мнение