Дерево категорий одной таблицей

Таблица значений в дерево значений.

Есть Таблица значений. Колонки у нее такие: категория,родительская категория,наименование категории. Нужно ее перевести в дерево значений. Какой алгоритм, а то бьюсь — не получается, пока только отсортировал таблицу :).
Желательно чтоб работало побыстрее, потому что кое как циклом и я сделаю но будет скорей всего медленно.

Нет, это не справочник. Таблицу я получил из ТЕКДОКА(система подбора запчастей для автомобилей). Теперь нужно постоить дерево категорий запчастей.

тогда сделай так чтобы «категория» была индентификатором строки в ТЗ.
После первого цикла посчитаешь уровень вложенности для каждого элемента и максимально вложенный элемент, потом начнешь строить дерево с самого верхнего

Затупил, 300х2, после первого цикла сортируем во уровню вложенности или передаем таблицу в запрос и группируем по уровню вложенности, и после второго цикла уже получаем дерево

// выше должен быть код как мы переделываем таблицу чтобы индентификатор строки было значений поля «Категория»

ТаблицаЗначений.Колонка.Добавить(«Уровень»);
Для каждого СтрокаТаблицы Из ТаблицаЗначений Цикл

Уровень = 0;
СтрокаРодителя = ТаблицаЗначений.Получить(СтрокаТаблицы.РодительскаКатегория)
Пока СтрокаРодителя <> Неопределено Цикл
СтрокаРодителя = ТаблицаЗначений.Получить(СтрокаРодителя.РодительскаКатегория);
Уровень = Уровень + 1;
КонецЦикла;

ТаблицаЗначений.Сортировать(«Уровень»);
ДеревоЗначений = Новый ДеревоЗначений;

Кое какие подвижки есть, правда сделал по другому. Обычно когда что то долго не получается лучше оставить на завтра, но хочется добить. Сейчас другой косяк.
Есть на форме элемент таблица значений. Тип значения — деревозначений, то есть именно здесь я и хочу отображать свое дерево. но мне нужно чтоб отображалась только одно колонка, в переменной деревозначений у меня 3 колонки. Добавить колонку в сам элемент не срабатывает, создатьколонки() — создает 3 колонки. Попытался видимость колонок устанавивать в ложь но тогда вообще дерево не работает пропадают плюсики.
КАК быть?

(13) «Есть на форме элемент таблица значений. Тип значения — деревозначений,» — так и не понял, о чем это.

Запрос = Новый Запрос;
Запрос.Текст =
«ВЫБРАТЬ
| МояТаблицаЗначений.Реквизит1,
| МояТаблицаЗначений.Реквизит2,
| МояТаблицаЗначений.Реквизит3
|ПОМЕСТИТЬ МояТаблица
|ИЗ
| &МояТаблицаЗначений КАК МояТаблицаЗначений
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| МояТаблица.Реквизит1 КАК Реквизит1,
| МояТаблица.Реквизит2 КАК Реквизит2,
| МояТаблица.Реквизит3 КАК Реквизит3
|ИЗ
| МояТаблица КАК МояТаблица
|ИТОГИ ПО
| Реквизит1,
| Реквизит2,
| Реквизит3″;

Запрос.УстановитьПараметр(«МояТаблицаЗначений», МояТаблицаЗначений);
РезультатЗапроса = Запрос.Выполнить();
МоеДеревоЗначений = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

ТЗ из ТЧ документа? тогда типизация не нужна, можно как здесь:
v8: Добавить таблицу значений в запрос

Читайте также:  Изготовление из дерева братины

(1)(14)(15) В кои веки 1С-запросы (ИЕРАРХИЯ) помогали преобразовать таблицу вида (Отец, Сын, Поле1, поле2. ) в дерево значений? СКД с Хрусталевским примером на 323 странице — это может быть, но я бы для решения этой задачи другой подход использовал.
(0) Для этого нужна РЕКУРСИЯ, на вход которой подаются «корневые» строки ТЗ — строки в которых Отец = Пусто, ну а далее — ищем для каждой строчки дочерние строки (где сын = отец, внук, правнук и пр.). Смысл понятен? Нагуглить сможешь?

(20) Если иерархия загнана в справочник или если дерево имеет фиксированный уровень вложенности — это поможет, но в топике ситуация явно другая.

Первым делом я гуглил. Так как когда у меня были деревья по программе в универе деревья я был далек от программирования и все списал то тему не усвоил. За 2 года эта надобность возникла в первый раз. Можно конечно отбросить мою обработку и погрузиться в дебри деревьев, но не сильно хочется.
Пока сделал вот так, предварительно отсортировав таблицу по значению родительскаякатегория.
СтруктураПоиска = Новый Структура;
МассивНайденныхСтрок = Новый Массив;
Для К = 0 По ДеревоКатегорийТаблица.Количество() — 1 Цикл
СтруктураПоиска.Вставить(«Категория»,ДеревоКатегорийТаблица[К].РодительскаяКатегория);
МассивНайденныхСтрок = ДеревоКатегорий.Строки.НайтиСтроки(СтруктураПоиска,Истина);
Если массивНайденныхСтрок.Количество() > 0 Тогда
ДеревоКатегорийСтрокаРодитель = МассивНайденныхСтрок[0];
Иначе
ДеревоКатегорийСтрокаРодитель = ДеревоКатегорий;
КонецЕсли;
ДеревоКатегорийСтрока = ДеревоКатегорийСтрокаРодитель.Строки.Добавить();
ДеревоКатегорийСтрока.Категория = ДеревоКатегорийТаблица[К].Категория;
ДеревоКатегорийСтрока.РодительскаяКатегория = ДеревоКатегорийТаблица[К].РодительскаяКатегория;
ДеревоКатегорийСтрока.НаименованиеКатегории = ДеревоКатегорийТаблица[К].НаименованиеКатегории;
КонецЦикла;
Вроде работает, буду проверять на предмет непредвиденных багов.

Источник

Использование одной модели и нескольких таблиц в Yii2 при построение дерева категорий по шаблону ClosureTable

Являюсь новичком в программировании. Чуть меньше года назад начал использовать первую версию фреймворка Yii для построения панели управления сайтом. Привлекла хорошая документация на русском языке и множество примеров кода через поисковики. С выходом Yii2, стал переписывать панель управления на нём, учитывая просчёты в проектировании прошлой панели. Для использования категорий (новостей, галереи, комментариев) решил использовать построение дерева категорий по шаблону проектирования ClosureTable. (Подробнее об этом шаблоне и сравнение с другими можете прочитать в публикации «Хранение деревьев в базе данных. Часть первая, теоретическая»).

Кратко об исходных данных при построении модуля новостей.

таблицы (приведу ключевые поля для данного шаблона проектирования):
news — таблица новостей. (поля: news_id — id-шник новости, category_id — id-шник категории новости и др.)
news_category — таблица категорий. (поля category_id — id-шник категории и др.)
news_category_tree — таблица дерева категорий. (поля ancestor — предок (или родитель), descendant — потомок (или дочерний), length — длина связи, других полей здесь нет)

Читайте также:  Снековая компания белое дерево

При генерации кода Gii создал модуль со следующими моделями (namespace app\modules\news\models):
News — модель новостей
NewSearch — модель поиска новостей (в yii2 для поиска используется модель наследуемая от основной, т.е. NewsSearch extends News)
NewsCategory — модель категорий
NewsCategorySearch — модель поиска категорий
NewsCategoryTree — модель дерева.

При написании кода, использование шаблона ClosureTable действительно позволило легко управлять категориями, имею в виду создание новых, перемещение по дереву существующих и удаление вместе с дочерними категориями.

Приведу примеры кода модели NewsCategoryTree:

$connection = \Yii::$app->db; $command = $connection->createCommand(' INSERT INTO '.self::tableName().' (ancestor, descendant, length) SELECT ancestor, :insert_category_id, length+1 FROM '.self::tableName().' WHERE descendant = :ancestor_category UNION ALL SELECT :insert_category_id, :insert_category_id, 0 '); $command->bindValues([':insert_category_id'=>$insert_category_id,':ancestor_category'=>$ancestor_category]); try < $command->execute(); > catch (Exception $e) < throw new Exception(Yii::t('admin','Failed to update the category tree []',['message'=>$e->getMessage()])); > 
$connection = \Yii::$app->db; $command_del = $connection->createCommand(' DELETE ct1 FROM '.self::tableName().' ct1 INNER JOIN '.self::tableName().' ct2 ON ct1.descendant=ct2.descendant LEFT JOIN '.self::tableName().' ct3 ON ct3.ancestor=ct2.ancestor AND ct3.descendant=ct1.ancestor WHERE ct2.ancestor=:update_category_id AND ct3.ancestor IS NULL '); $command_del->bindValue(':update_category_id', $update_category_id); $command_ins = $connection->createCommand(' INSERT INTO '.self::tableName().' (ancestor, descendant, length) SELECT ct1.ancestor, ct2.descendant, ct1.length+ct2.length+1 FROM '.self::tableName().' ct1 JOIN '.self::tableName().' ct2 WHERE ct2.ancestor=:update_category_id AND ct1.descendant=:new_ancestor '); $command_ins->bindValues([':update_category_id'=>$update_category_id,':new_ancestor'=>$new_ancestor]); $transaction = $connection->beginTransaction(); try < $command_del->execute(); $command_ins->execute(); $transaction->commit(); > catch (Exception $e) < $transaction->rollback(); throw new Exception(Yii::t('admin','Failed to update the category tree []',['message'=>$e->getMessage()])); > 

Для удаления (этот код не обязателен, поскольку при удалении категории, удаляются связанные записи дерева категорий):

$connection = \Yii::$app->db; $command = $connection->createCommand(' DELETE ct1 FROM '.self::tableName().' ct1 INNER JOIN '.self::tableName().' ct2 ON ct1.descendant=ct2.descendant WHERE ct2.ancestor= :delete_category_id '); $command->bindValue(':delete_category_id', $delete_category_id); try < $command->execute(); > catch (Exception $e) < throw new Exception(Yii::t('admin','Failed to update the category tree []',['message'=>$e->getMessage()])); > 

Возникшая проблема1:
Однако, при таком варианте для других модулей, которые будут использовать такой же принцип построения дерева по шаблону ClosureTable, необходимо будет создавать для каждого свою модель управления деревом (CatalogCategoryTree, CommentsTree и т.д.). Эти модели в узком смысле буду различаться только заданием используемой таблицы в базе данных.

Читайте также:  Время обработки деревьев инсектицидами

Решение1:
Для того, чтобы не плодить кучу моделей с одинаковым кодом, весь код был перенесён в одну модель ClosureTree (namespace common\models). Common для того, чтобы её можно было использовать для нескольких приложений по типу advanced.

Возникшая проблема2:
Тут то и выяснилось, что изменять имя таблицы «на лету» не получается из-за того, что init() модели ClosureTree не позволяет передавать в него дополнительные атрибуты из модели NewsCategory (по сути менять таблицу одной модели из другой модели):

public function init($table_name,$category_class_name) < parent::init(); $this->_tableName=$table_name; $this->_categoryClassName=$category_class_name; > 
$classTree=new ClosureTree('>',self::className()); 

выводя ошибку «Declaration of common\models\ClosureTree::init() should be compatible with yii\db\BaseActiveRecord::init()». Которая говорит, что init() не должен принимать параметры, поскольку у родительского класса init() не принимает параметры.
И мало того, при использование статического метода запроса данных (ClosureTree::find()) не возможно было бы задать название таблиц.

Решение2:
Для обхода этого недоразумения, оказалось достаточно использовать статическое задание названия таблицы в модели ClosureTree и функции устанавливающей это статическое значение (дополнительно устанавливается класс, который отвечает за таблицу категорий NewsCategory для обеспечения связей):
ClosureTree:

public static $_tableName; // название таблиц (news_category_tree, calalog_category_tree, comments_tree и т.д.) public static $_categoryClassName; // класс категорий, который использует модель дерева (NewsCategory, CatalogCategory и т.д.) /* Задаём таблицу с деревом и класс, который отвечает за эту таблицу */ public function setTableAndClass($table_name,$category_class_name) < self::$_tableName=$table_name; self::$_categoryClassName=$category_class_name; >public static function tableName()

Для задания имени таблицы и класса категорий, используем функцию init() класса категорий:
NewsCategory:

public function init() < parent::init(); /* настраиваем модель ClosureTree для работы с $this классом категорий и с таблицей news_category_tree */ $classTree=new ClosureTree(); $classTree->setTableAndClass('>',self::className()); > 

Отсюда имеем, что при вызове класса категорий (NewsCategory), автоматически настраиваем под него модель ClosureTree.
Теперь можно использовать ClosureTree::find(), он уже знает какую таблицу ему надо использовать.

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

Надеюсь, моя статья будет полезна новичкам.

Источник

Оцените статью