Доброго времени суток, столкнулся с рядом проблем после прикручивания libmysql к билдеру, а именно требуется вывод базы в виде дерева (TTreeView), подскажите новичку как лучше и быстрее организовать этот вывод. Структура базы такая: Код (Text): "CREATE TABLE IF NOT EXISTS `categories` ( `ID` int(11) NOT NULL auto_increment, `SUBID` int(11) NOT NULL default '0', `NAME` text NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;" Как я сделал: ID - Уникальный айдишник каждого нода, что бы не париться я в ComCtrls.hpp прикрутил пару пропертей в классе TTreeNode (int NodeID), SUBID - как вы поняли это ID родительского нода, чтобы в дальнейшем строить дочерние ноды в дереве - с этим и есть проблема. Я решил пойти таким путём - сначала выдирать самые первые ветки дерева (у которых SUBID будет равен нулю), а дальше исходя из общего количества записей в базе и уже созданных нодов (TTreeView->Items->Count) прыгать на начало кода и уже перебирать заново, но уже с сортировкой по SUBID (исходя из каждого TTreeView->Items->NodeID) - и так потихоньку заполнять дерево. Затея очень бредовая, очень много бестолковых обращений к базе, пожалуйста подскажите путь оптимизации.
Структура базы такая: Cтруктура таблицы в данном случае, для более-мение коректного решения задачи, нужно редизайнить структуру БД так что бы было 2 таблицы, одна из которых характеризует "директорию", а друга характеризует "под директорию".
Всё дело в том, что список должен содержать произвольное (ничем не ограниченное) количество "поддиректорий" - вот и пляски с бубном.
Тогда такая структура, насколько я понимаю, не будет отвечать НФ, по идее подобного результата можно добиться связав предложеные мною выше таблицы, через промежуточную таблицу, тем самым реализовав связь "многие-ко-многим" между двумя данными таблицами.
Не совсем понял ) как это будет выглядеть и как должна такая структура работать ? может загружать БД полностью в память и уже в программе сортировать векторами ?..
Да, примерно где-то так Структура получиться довольно сложная, можно решить проблему другим путем, если добавить в таблицу "категории" поле которое являеться ID "под категории" и будет описывать вложенность списков. Если нужно могу набросать примерно структуру БД в данном случае.
По идее где-то так, покрайней мере, будут разделены категории и подкатегории, и будет удобней делать выборку. Код (Text): /* в id родительской категории записываем id категории в которой нужно добавить еще одну родительскую подкатегорию если родительских категорий нет то 0 */ CREATE TABLE IF NOT EXISTS `category` ( `id` int(5) unsigned NOT NULL AUTO_INCREMENT, /* id категории */ `top_id` int(10) unsigned NOT NULL, /* id родительской категории */ `name` varchar(64) NOT NULL, /* имя категории */ PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; CREATE TABLE IF NOT EXISTS `category_sub_category` ( `top_id` int(10) unsigned NOT NULL, /* id категории*/ `sub_id` int(10) unsigned NOT NULL /* id под категории */ ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE IF NOT EXISTS `sub_category` ( `id` int(11) NOT NULL, /* id по дкатегории */ `name` varchar(64) NOT NULL /* название подкатегории */ ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Wranger, вы б колонку SUBID назвали ParentID, а то других путаете и сами через несколько месяцев запутаетесь, когда будете вспоминать, как работает. "Суб" все-таки подразумевает "под", а у вас ссылка на ID "над", на родителя. По делу: заведите дополнительное строковое поле длины так побольше, в этом строковом поле записывайте путь из ID нодов, начиная с родительского, причем ID каждого нода в строке формируйте в виде int фиксированной длины. Например, вот такие могут быть записи (ID,SUBID,NAME,PATH) (1,NULL,'Корень А','000001') (2,NULL,'Корень Б','000002') (3,1,'Поддиректория А1','000001 000003') (4,1,'Поддиректория А2','000001 000004') (5,2,'Поддиректория Б1','000002 000005') (6,3,'Поддиректория второго уровня А1_1','000001 000003 000006') (7,2,'Поддиректория Б2','000002 000007') То есть, поле PATH надо заполнять, копируя PATH родителя и добавляя свой ID через пробел. Теперь можно одним SELECT выбрать сразу все записи, отсортировав их по полю PATH (1,NULL,'Корень А','000001') (3,1,'Поддиректория А1','000001 000003') (6,3,'Поддиректория второго уровня А1_1','000001 000003 000006') (4,1,'Поддиректория А2','000001 000004') (2,NULL,'Корень Б','000002') (5,2,'Поддиректория Б1','000002 000005') (7,2,'Поддиректория Б2','000002 000007') Как видите, теперь можно пройтись рекурсивно по результатам, оставаясь на заданном уровне рекурсии если родитель не меняется, углубляясь, если родителем становится предыдущий ID, или иначе выходя на предыдущий уровень рекурсии.
Как-же НФ ? Аномалии обновления, удаления ? Нужно будет поддерживать целостность данных, используя пограмный интерфейс, а не возможности СУБД, если я правильно вас понял.
Кстати, лучше наверное даже PATH не из ID составлять, а из номеров упорядочивания внутри директории (вы ведь наверное захотите, чтоб ноды имели не только иерархию, но произвольно заданный порядок внутри каждой папки). В данном случае к родительскому пути надо будет добавлять уже свой номер упорядочивания внутри родителя (ID,SUBID,NAME,ORDERING,PATH): (1,NULL,'Корень А',1,'000001') (3,1,'Поддиректория А1',1,'0000001 0000001') (6,3,'Поддиректория второго уровня А1_1',1,'0000001 0000001 0000001') (4,1,'Поддиректория А2',2,'0000001 0000002') (2,NULL,'Корень Б',2,'0000002') (5,2,'Поддиректория Б1',1,'0000002 0000001') (7,2,'Поддиректория Б2',2,'0000002 0000002')
Периодически выполнять проверку целостности рекурсивными запросами и "чинить" поля PATH. Ну а при нормальных обновлениях - конечно обязательно корректировать PATH. То есть, такая реализация замедляет изменение, но ускоряет процесс чтения содержимого
Ну а если все-таки ограничиться разумным не очень большим количеством уровней вложенности, то можно просто лефтджойновый селект делать, если, хе хе, SQL-сервер не подавится цепочкой джойнов
Спасибо за предложенные идеи, конечно в теории всё просто - но в билдере придётся хорошо покопать treeview - сказать хотел еще, что патчи ("пути") в базе - не очень хорошее решение как для базы так и для программы, больше путанницы, и забыл добавить, что я имел ввиду - по какой схеме (в рамках С++ и VCL) исходные данные распихать, кстате подумываю в сторону промежуточной базы - думаю вариант наиболее нейтрален к ресурсам программы и СУБД одинаково )
На мое мнение целостность данных, должна контролироваться СУБД, а не програмным интерфейсом. Так как это одна из основных функций СУБД, помимо хранения данных. В особенности, если брать реляционную модель данных, то есть понятие атомарности данных, которое являеться одним из важных при посторении структуры БД, и что идет в разрез с Но для решения данной задачи, при условии слабой нагрузки, отсутствия других данных опираюшихся на "категории" данный вариант привлекает преступной простотой реализации