Древовидная структура + статус активности

sverel

Новичок
Древовидная структура + статус активности

Задача: реализовать древовидную структуру каждый нод который имеет свой статус активности (опубликован или нет). Например, дерево категорий в интернет-магазине.

- Инструмент:
  • Отвёртки
  • Кисточки
    - Плоские кисти
    - Круглые кисти
  • Дрели

Делать деревья - не проблема. Nested sets рулит :)

Статус активности - тоже не проблема. Отдельное поле TINYINT(1).

Проблема начинается при совмещении NESTED_SETS и статуса активности, ведь статус активности должен наследоваться всеми дочерними нодами дерева. Т.е. если я группе "инструменты" ставлю статус=0 (не показывать), то все дочерние подразделы должны тоже пропасть с сайта. Но т.к. для построения дерева используется запрос:

PHP:
SELECT * FROM `tbl`
WHERE `active`=1
ORDER BY `left_key`
То группа "инструменты" не попадает под фильтр, однако "отвёртки", "кисточки", "дрели" попадают и выводятся на сайте.

Можно при смене статуса ставить этот же статус всем дочерним элементам. Но в этом случае, теряется индивидуальные статусы у всех дочерних. Например, "кисточки" и "дрели" были не активными и внутри "инструментов" отображались только "дрели". Потом я "инструменты" сначала деактивировал, а потом снова активировал. В итоге все дочерние становятся активными и теперь и "кисти" и "отвёртки" выводятся на сайте.

Пример реализации этой задачи можно найти в ФотоШопе со слоями и группами. Можно деактивировать группу и все дочерние слои исчезнут. Но при активации группы все дочерние слои сохранят свой статус активности и те кто были неактивны - такими и останутся.

Можно конечно вместо Nested Sets заюзать алгоритм ID -> PARENT_ID, но в дереве 200 нодов и выводится оно целиком на каждой странице сайта. Выполнять 200 SQL-запросов для каждой страницы - это жесть.
Ещё можно используя NESTED_SETS на этапе построения дерева в проходе по массиву проверять: есть ли у текущего элемента активный родитель, про-родитель, ...., про-про-про-родитель. Но это тоже жеесть, ведь для элемента "плоские кисти" надо проверить активность "кисточек" (родительская) и "инструментов" (про-родитель). А как это сделать в плоском массиве? Извратится конечно технически можно, написав скрипт в 20 строчек, но сдаётся мне это жопачное решение такое же как и ID -> PARENT_ID.


Есть какие-нибудь "правильные" решения у данной задачи?
 

Adelf

Administrator
Команда форума
1) WHERE `active`=1 - убрать и реализовать довольно простенькую проверку в php(там довольно просто, ибо родители всегда идут раньше).
2) ID -> PARENT_ID - почему нет? кеширование решит проблему тормозов.
 

Fortop

Новичок
ID -> PARENT_ID, но в дереве 200 нодов и выводится оно целиком на каждой странице сайта. Выполнять 200 SQL-запросов для каждой страницы - это жесть.
Зачем 200 запросов? Одного разве недостаточно?
Какой глубины дерево?
 

sverel

Новичок
dimagolov,
И что хранить в `parent_active` для группы "плоские кисти"? Статус группы "кисти" или "инструменты"? В примере специально 3-х уровневое дерево - думал, догадаетесь, что `parent_active` - это не выход для многоуровневого дерева.


Fortop, как это дерево связанное по id -> parent_id можно вывести один запросом? Дерево любой глубины. В примере 3-х уровневое.


- Инструмент:
* Отвёртки
* Кисточки
- Плоские кисти
- Круглые кисти
* Дрели
 

dimagolov

Новичок
sverel, почему не выход? тебе нужно решить 2 задачи:
1. запоминать статус активности каждого узла
2. отбирать только те узлы, у которых все родители активны и сами они активны.

то есть parent_active ставиться в 0 для всех детей данного узла.
 

Fortop

Новичок
Fortop, как это дерево связанное по id -> parent_id можно вывести один запросом? Дерево любой глубины.
LEFT JOIN'ами. таблицы самой на себя.
Там есть нюанс, что число JOIN ограничено 32мя, впрочем это может отличатся для разных версий БД.
 

sverel

Новичок
dimagolov, это не сработает. Сначала я деактивирую "инструменты" и все ДОЧЕРНИЕ получают `parent_active`=0. Потом я активирую "кисточки" (в админке всё дерево видно не зависимо от статуса) и все её дочерние ("плоские" и "круглые") получают parent_active=1 и опять выводятся на сайте, хотя их про-родитель всё ещё не является активным.


Fortop, 32 left-joina - это не на много круче чем 200 запросов :) В конечном итоге кол-во проходов по таблице получится 200, просто не придётся для этого посылать 200 SQL-запросов на SQL-сервер.
 

dimagolov

Новичок
Потом я активирую "кисточки" (в админке всё дерево видно не зависимо от статуса) и все её дочерние ("плоские" и "круглые") получают parent_active=1
с какой радости? смотришь, что у "кисточки" parent_active=0 и просто не меняешь parent_active для детей

-~{}~ 26.02.10 12:11:

В конечном итоге кол-во проходов по таблице получится 200
да ну? если поле по которому джойнимся имеет индекс?
 

sverel

Новичок
dimagolov, опять получаем, что надо писать алгоритм выуживания родителя в плоском двухмерном массиве. Хотя в админке это не так критично для производительности, но всё же намного сложнее чем простой апдейт одной ветки:

PHP:
UPDATE `tbl` SET `parent_active`=1
WHERE
   left_key >= $left_key AND
   right_key <= $right_key;
Неужели нет более изящного решения?
 

dimagolov

Новичок
надо писать алгоритм выуживания родителя
нет, нужно всегда отбирать из базы `parent_active` для ТЕКУЩЕГО узла и исходя из него решать обновлять или нет это поле для детей при изменении статуса текущего узла.
 

Beavis

Banned
а нафига это всё делать SQL-ем? у тебя же есть какая то логика для вывода дерева? вот туда и засунь эту небольшую проверочку
 

Adelf

Administrator
Команда форума
>> Да! Точно! Так и сделаю. Спасибо!

И как уже не в первый и не в десятый раз ТС просто не обращает внимания на первый же совет :) Который точно такой же.
 

zerkms

TDD infected
Команда форума
а ещё можно просто добавить связанный подзапрос в условие (или inner join, не суть важно), который для текущего узла выгребет все родительские флажки активности

SELECT * FROM `tbl`
WHERE `active`=1 AND
(SELECT MIN(`active`) FROM `tbl` WHERE тут условие на получение всех родителей)
ORDER BY `left_key`

родители получаются вроде как 1.lkey < 2.lkey AND 1.rkey > 2.rkey, не совсем уверен
 

sverel

Новичок
Элегантное решение, но возвращаемся опять к 200 проходам по таблице по 1 для каждого узла. Проще использовать id -> parent_id - там хотя бы условие проще => нагрузк меньше.
 

zerkms

TDD infected
Команда форума
sverel
это просто было ещё одно решение чтобы покрыть всё множество (решений).

ps: я бы не стал однозначно утверждать, что adjacency list проще.
у тебя же есть какая то логика для вывода дерева? вот туда и засунь эту небольшую проверочку
Да! Точно! Так и сделаю. Спасибо!
то же самое можно было бы сделать и на nested sets.
а вот с AL ты заметно усложнишь выборку, точнее - разбор результатов выборки.
 

sverel

Новичок
моя реплика относилась к dimagolov
___________
нет, нужно всегда отбирать из базы `parent_active` для ТЕКУЩЕГО узла и исходя из него решать обновлять или нет это поле для детей при изменении статуса текущего узла.
___________

-~{}~ 27.02.10 10:57:

А насчёт проверки в момент вывода - я ещё в самом первом посту написал в конце. Видимо, Beavis не дочитал до конца вопрос.
 

Beavis

Banned
Автор оригинала: sverel

А насчёт проверки в момент вывода - я ещё в самом первом посту написал в конце. Видимо, Beavis не дочитал до конца вопрос.
я просто не понял, а в чем проблема то так сделать?? это же пара строчек всего

-~{}~ 27.02.10 11:15:

просто вводишь в цикле флаг видимости, и когда встречается невидимый элемент, запоминаешь его уровень, и пока не встретится видимый элемент с уровнем не больше того который запомнил, ничего не выводишь
 
Сверху