Принципы управления я буду описывать только на уровне базы данных. База данных: PostgreSQL, потому что делать все тоже самое в MySQL, в некоторых случаях невозможно, а в некоторых гемморойно, но никогда не просто. Возможно позднее я рассмотрю более детально работу в MySQL.
Правила
На первый взгляд управление деревом Adjacency List довольно простое, но эта простота, зачастую природит к определенным проблемам, так очень просто, можно назначить подчинение первого узла второму, а второго первому, что может привести к бесконечному зацикливанию при выборе дерева, можно назначить родителем узла несуществующий ID и тогда ветка полностью исключится из дерева. Соответсвенно отсюда определим правила:
Узел может подчиняться только существующему узлу;
Узел не может подчиняться потомку, что очевидно;
Решения
С первым правилом, казалось бы просто, но, если при установке родителя мы можем проверить его существование,
то при удалении родителького узла, нам потребуется провести обновление/удаление подчиненных узлов. Для этого
рекомендуют использовать внешний ключ (FOREING KEY) на таблицу, но и это не решит всех проблем. Так,
если нам потребуется не удалять каскадно потомков, то мы сможем с помощью FOREIGN KEY перевести потомков
только в корень дерева, но не на уровень выше.
Впрочем, все решаем по ситуации, создаем внешний ключ:
С первым правилом более-менее разобрались. Правило второе: потомок узла не может быть его родителем.
Увы, в отличие от Nested Sets в Adjacency List нет координат узла, максимум, что мы можем получить -
это зависимость на уровень вверх и зависимость на уровень вниз, поэтому будем производить проверку выбрав
родительскую ветку узла, в подчинение которому ставим текущий узел. Для этого создадим триггер на обновление:
Для тех у кого PostgreSQL ниже 8.4 воспользуемся процедурой получения родительской ветки (ссылка):
Вставку и удаление контролирует внешний ключ, так что можно насладится результатом проделанной работы.
Денормализация
Зачастую, определенные параметры узлов дерева требуются достаточно часто, а вычисление их достаточно ресурсоемко. Для этого потребуется ввести дополнительные денормализованные поля таблицы, которые зависят от внешних факторов. Так добавим поле уровень узла и количество подчиненных узлов.
Если со вторым полем (количество потомков) более менее все просто, то в первым (уровень узла) несколько сложнее: так, при смене уровня узла потребуется обновить уровни всех узлов-потомков, поэтому, если предполагается постоянное перемещение узлов по уровням и поле level не так актуально, то лучше от него отказаться.
Определение значений денормализованных полей на уровне приложения совершенно бессмысленно, так как это добавляет дополнительные точки отказа, отъедает достаточно много ресурсов, а так же выводит на более высокий уровень программирования логику связанную с низкоуровневыми неявными определениями.
Рассматриваем только уровень триггеров, собственно, по большей части, именно для этого они и предназначены. Итак, добавляем в таблице два дополнительных поля level и counter и создаем триггеры:
Как видно, все просто, если не считать "финты ушами" с NULL, именно по этому, я рекомендую использовать определение NULL только когда это действительно необходимо;
Обновление сложнее, и изменение денормализованных полей мы не можем ограничить. Поле level обновляется практически рекурсивно, но, его использование помогает нам защитится от возможных зацикливаний;
Раз уж денормализировали таблицу, то можно сделать удаление узлов наиболее кошерным :-). Так, есть три варианта удаления узлов:
Каскадное удаление, когда удаление узла удаляет его потомков;
Перенос прямых потомков в корень дерева;
Перенос прямых потомков на уровень выше;
Включать, выключать тригееры и внешние ключи перед каждым запросом, естественно не получится. Остается ввести дополнительные пользовательские переменные. Так в конфиге PostgerSQL добавляем раздел, если его еще нет:
По-умолчанию поставим cascade - каскадное удаление. Для остальных действий обозначим значения переменных как: root и levelup.
Естественно, что при использовании этих триггеров, надобность во внешнем ключе отпадает, он будет только мешать. Остается уточнить как правильно производить удаление.
В PostgreSQL пользовательские установки, частности наши user_vars можно изменять локально в пределах транзакции, соответсвенно можно не переживать по поводу того, что изменение установок распространится на другие соответсвующие запросы:
Но можно сделать еще проще, без явного использования транзакции:
На этом все. Решение для MySQL я рассмотрю по возможности позднее, хотя желания, если четно нет.