[ Поиск ] - [ Пользователи ] - [ Календарь ]
Полная Версия: Создание древовидной структуры
Snus
Добрый вечер, господа программисты!
Все рано или поздно сталкивались с древовидной структурой данных из SQL. Я уверен, что каждого волнует вопрос какое решение создания древовидной структуры наиболее оптимальнее и менее ресурсоемко. Я в этом вопросе не исключение и хотелось бы узнать ваши варианты решения задачи.
После напишу какое решение придумал я. (чтобы не позориться(PS: хотя как знать... я считаю, что оно оптимальнее тех, что я нарыл в рунете))



Спустя 42 минуты, 34 секунды (15.04.2011 - 00:48) kirik написал(а):
Тут док по MySQL, а эт когда я задавался подобным вопросом

Спустя 32 минуты, 46 секунд (15.04.2011 - 01:21) Snus написал(а):
kirik
Я читал ту документацию.
Мое решение схоже с твоим :)

DUMP

CREATE TABLE IF NOT EXISTS `cms_goods` (
`gId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID товара',
`pId` int(11) NOT NULL COMMENT 'ID родителя',
`gCode` varchar(11) DEFAULT NULL COMMENT 'Код',
`gArticle` varchar(50) DEFAULT NULL COMMENT 'Артикул',
`gNomenclature` varchar(200) DEFAULT NULL COMMENT 'Номенклатура',
`gUnitId` int(3) DEFAULT NULL COMMENT 'ID: Базовая единица измерения',
`folder` int(1) DEFAULT NULL COMMENT 'Папка \\ файл',
`userEdit` int(11) DEFAULT NULL COMMENT 'ID пользователя',
`dateEdit` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Дата изменения',
PRIMARY KEY (`gId`),
UNIQUE KEY `gCode` (`gCode`)
)
ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Магазин' AUTO_INCREMENT=10014 ;

INSERT INTO `cms_goods` (`gId`, `pId`, `gCode`, `gArticle`, `gNomenclature`, `gUnitId`, `folder`, `userEdit`, `dateEdit`) VALUES
(10001, 0, 'АГ000000001', NULL, 'ТОВАРЫ', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10002, 10001, 'АГ000000015', NULL, 'АКСЕССУАРЫ', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10003, 10002, 'АГ000000506', NULL, 'Аксессуары Procar', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10004, 10003, 'АГ000000747', 'Пенная насадка', 'Пенная насадка', 1, NULL, NULL, '2011-04-14 17:05:04'),
(
10005, 10003, 'АГ000000507', 'LC/50', 'Пистолет для пеногенератора 50 см (желез. ручка)', 1, NULL, NULL, '2011-04-14 17:05:04'),
(
10006, 10003, 'АГ000001407', 'PA/50', 'Пистолет для пеногенератора 50 см (пласт. ручка)', 1, NULL, NULL, '2011-04-14 17:05:04'),
(
10007, 10002, 'АГ000000586', NULL, 'Аксессуары для компрессоров', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10008, 10007, 'АГ000001306', '60 D-5 бс', 'Пистолет для подкачки шин (быстросъём)', 1, NULL, NULL, '2011-04-14 17:05:04'),
(
10009, 10003, 'АГ000000680', 'PA/75', 'Пистолет для пеногенератора 75 см (пласт. ручка)', 1, NULL, NULL, '2011-04-14 17:05:04'),
(
10010, 10001, 'АГ000000094', NULL, 'Бытовая химия', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10011, 10002, 'АГ000000083', NULL, 'Аксессуары к уборочному оборудованию', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10012, 10001, 'АГ000000016', NULL, 'Запчасти', NULL, 1, NULL, '2011-04-14 17:05:04'),
(
10013, 10010, 'АГ000001059', NULL, 'Губки поролоновые', NULL, 1, NULL, '2011-04-14 17:05:04');


PHP

	

// @@ mysql_connect ...

function cmp($a, $b){
return strcmp(trim($a['index']), trim($b['index']));
};

function getIndexes($arr, $pId, $depth){
$indexes = $pId;
for($i = 1; $i < $depth; $i++){
$ppId = $pId = $arr[$pId]['pId'];
$indexes = $ppId.' '.$indexes;
}
return $indexes;
};

function getSorted($arr){
foreach($arr as &$row){
$indexes = !empty($row['pId']) ? getIndexes($arr, $row['pId'], $row['depth']) : 0;
$row['index'] = $indexes.' '.$row['gId'];
}
usort($arr, 'cmp');
return $arr;
};

list($pId, $gId) = !empty($_GET['p']) ? explode('_', $_GET['p']) : array('0','0');

$query = "
SELECT `gId`, `pId`, `gCode`, `gArticle`, `gNomenclature`, `folder`
FROM `cms_goods`
ORDER BY `gId`"
;

$sql = mysql_query($query) or die(mysql_error());
while ( $row = mysql_fetch_assoc($sql) ){
$arr[$row['gId']] = $row;
$arr[$row['gId']]['depth'] = !empty($arr[$row['pId']]['depth']) ? $arr[$row['pId']]['depth'] + 1 : 1;
}

$arr = getSorted($arr);

foreach($arr as $row){

echo '<div style="font-size: 9pt; font-family: Arial; margin-left: '.($row['depth'] * 30).'px;'.($row['folder'] ? ' font-weight: bold;' : '').'">';
echo '<a href="?p='.($row['folder'] ? $row['gId'] : $row['pId'].'_'.$row['gId']).'">'.$row['gNomenclature'].'</a>' ;
echo '</div>';

if(!empty($gId) && $gId == $row['gId'] && !empty($row['gArticle'])){
echo '<div style="font-size: 9pt; font-family: Arial; margin-left: '.($row['depth'] * 30 + 30).'px;">';
echo 'ART: '.$row['gArticle'];
echo '</div>';
}

}



Спустя 9 часов, 7 минут, 43 секунды (15.04.2011 - 10:29) Snus написал(а):
Что, ни у кого нет предложений и замечаний?

Спустя 11 минут, 31 секунда (15.04.2011 - 10:40) Семён написал(а):
Я делаю по идеи предложенной Котеровым.
Получать категории и программно строить древовидный массив, и если дерево большое просто кешировать. Способы с Nested Sets считал, считаю и надеюсь буду считать самым великим извращением какое я когда-либо встречал. (хотя лучше них в некоторых ситуациях решения не найти)

Собственно вот функция его для построения массива: может кому пригодиться:
    private function createTree($rows, $idName, $pidName) {
$children = array(); // children of each ID
$ids = array();
// Collect who are children of whom.
foreach ($rows as $i => $r) {
$row = & $rows[$i];
$id = $row[$idName];
if ($id === null) {
// Rows without an ID are totally invalid and makes the result tree to
// be empty (because PARENT_ID = null means "a root of the tree"). So
// skip them totally.

continue;
}
$pid = $row[$pidName];
if ($id == $pid)
$pid = null;
$children[$pid][$id] = & $row;
if (!isset($children[$id]))
$children[$id] = array();
$row['childNodes'] = & $children[$id];
$ids[$id] = true;
}
// Root elements are elements with non-found PIDs.
$forest = array();
foreach ($rows as $i => $r) {
$row = & $rows[$i];
$id = $row[$idName];
$pid = $row[$pidName];
if ($pid == $id)
$pid = null;
if (!isset($ids[$pid])) {
$forest[$row[$idName]] = & $row;
}
unset($row[$idName]);
unset($row[$pidName]);
}
return $forest;
}

Спустя 17 минут, 25 секунд (15.04.2011 - 10:58) Snus написал(а):
Просто передо мной стояла задача написать древовидную структуру, которая позволяла бы создавать неограниченное кол-во вложенностей (потомков) без изменения SQL-запроса. Пока не могу со 100-% точностью сказать насколько быстро, правильно и эффективно будет работать предложенный мною вариант, но я планирую довести его до ума и использовать во всех своих проектах. Ибо это универсальный и удобный способ.

Спустя 8 минут, 56 секунд (15.04.2011 - 11:07) Snus написал(а):
Семён
Цитата (Семён @ 15.04.2011 - 07:40)
private function createTree($rows, $idName, $pidName) {

Покажи какие данные он обрабатывает, в частности $rows что содержит.

Спустя 3 минуты, 54 секунды (15.04.2011 - 11:10) Семён написал(а):
$rows - содержит собственно массив например
[0]
[
'id'] = 1
['pid'] = 0
['name'] = 'Какегория 1'
[1]
[
'id'] = 2
['pid'] = 0
['name'] = 'Какегория 2'
[2]
[
'id'] = 3
['pid'] = 1
['name'] = 'Подкатегория - Категория 1'


В аргументы idName, $pidName, мы передадим ('id','pid') т.к. в базе колонки так называются

Спустя 19 секунд (15.04.2011 - 11:11) Trianon написал(а):
Есть несколько стандртных методик.
1. метод списков смежности (adjacency lists) id - parent_id
2. метод вложенных подмножеств (nested sets) left_boundary - right_boundary
3. метод матириализованного пути (matherialized path) :
01
0101
01010503
01010504
0102

Есть легкие вариации каждого из них (к примеру добавление номера уровня в структуру) , но основа именно такая.

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

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

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

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

Спустя 10 минут, 33 секунды (15.04.2011 - 11:21) Snus написал(а):
Trianon
Цитата (Trianon @ 15.04.2011 - 08:11)
3. метод матириализовнного пути (matherialized path) :
01
0101
01010503
01010504
0102

Этот вариант считаю наиболее удобным.
Собственно, его и избрал как основу.

На выходе работы скрипта получается следующее:

ТОВАРЫ (index: 0 10001)
АКСЕССУАРЫ (index: 0 10001 10002)
Аксессуары Procar (index: 0 10001 10002 10003)
Пенная насадка (index: 0 10001 10002 10003 10004)
Пистолет для пеногенератора 50 см (желез. ручка) (index: 0 10001 10002 10003 10005)
Пистолет для пеногенератора 50 см (пласт. ручка) (index: 0 10001 10002 10003 10006)
Пистолет для пеногенератора 75 см (пласт. ручка) (index: 0 10001 10002 10003 10009)
Аксессуары для компрессоров (index: 0 10001 10002 10007)
Пистолет для подкачки шин (быстросъём) (index: 0 10001 10002 10007 10008)
Аксессуары к уборочному оборудованию (index: 0 10001 10002 10011)
Бытовая химия (index: 0 10001 10010)
Губки поролоновые (index: 0 10001 10010 10013)
Запчасти (index: 0 10001 10012)

Memory Usage: 19.05 kb
Generate Time: 0.00095582008361816

Спустя 8 дней, 5 часов, 2 минуты, 18 секунд (23.04.2011 - 16:24) ASKNN написал(а):
Может кому-то поможет, сделано для комментов, но сути не меняет


function mapTree($dataset) {
$tree = array(); // Создаем новый массив
/*
Проходим в цикле по массиву $dataset, который был передан в качестве аргумента.
в $id будет попадать уникальный id комментария,
&$node - работаем со значением по ссылке
*/

foreach ($dataset as $id=>&$node) {
if (!$node['parent_id']) { // не имеет родителя, т.е. корневой элемент
$tree[$id] = &$node;
} else {
/*
Иначе это чей-то потомок,
этого потомка переносим в родительский элемент,
при этом у родителя внутри элемента создастся массив childs, в котором и будут вложены его потомки
*/

$dataset[$node['parent_id']]['childs'][$id] = &$node; //

}
}


return $tree;
}

Взято отсюда.

Подскажите, как организовать, чтобы "потомки" выстраивались в обратном порядке. Т.е. все элементы уже выстраиваются в обратном порядке сортировкой из базы, а нужно, чтобы "потомки" выстраивались в нормальном порядке, как на картинке.
user posted image

Спустя 2 часа, 11 минут, 48 секунд (23.04.2011 - 18:35) sergeiss написал(а):
Пропустил темку... Я выкладывал своё решение для Postgre http://phpforum.ru/index.php?showtopic=31806&hl=
На MySQL не думал, как сделать. Поэтому не знаю. Но мне кажется, что я бы сделал функцию в MySQL, внутри которой уже делал бы всё, что нужно. Там можно было бы сделать параметры для изменения порядка сортировки, ограничения выводимого уровня вложенности и т.д. и т.п. Из ПХП вызывался бы один только запрос.

Спустя 3 часа, 36 минут, 53 секунды (23.04.2011 - 22:12) ИНСИ написал(а):
Цитата
я бы сделал функцию в MySQL, внутри которой уже делал бы всё, что нужно

Очень было бы интересно посмотреть на такой вариант ......

Snus - Решил сетевой пирамидой заняться smile.gif

Спустя 19 минут, 31 секунда (23.04.2011 - 22:32) sergeiss написал(а):
Цитата (velbox @ 23.04.2011 - 23:12)
Очень было бы интересно посмотреть на такой вариант ......

Ключевое слово у меня было "бы" smile.gif Не хочу глубоко "втыкать" в MySQL, без особой необходимости.

Спустя 1 день, 15 часов, 25 минут, 20 секунд (25.04.2011 - 13:57) ASKNN написал(а):
Может кто-то платно готов помочь в решении моей задачи?

Спустя 2 месяца, 16 дней, 7 часов, 55 минут, 10 секунд (11.07.2011 - 21:52) John.Deff написал(а):
у меня решение неограниченной вложенности + вопрос:
есть значения в базе:

| id |--------| parent |--------| name |
+----+--------+--------+--------+------------+
| 1 |--------| 0 |--------| Родитель 1 |
| 2 |--------| 0 |--------| Родитель 2 |
| 3 |--------| 0 |--------| Родитель 3 |
| 4 |--------| 0 |--------| Родитель 4 |
| 5 |--------| 0 |--------| Родитель 5 |
| 6 |--------| 0 |--------| Родитель 6 |
| 11 |--------| 1 |--------| Потомок 1 |
| 12 |--------| 1 |--------| Потомок 2 |
| 13 |--------| 2 |--------| Потомок 1 |
| 14 |--------| 13 |--------| Потомок 1 |
| 15 |--------| 13 |--------| Потомок 2 |

выводим данные из БД
<?php
$cats = "SELECT id,name,parent FROM category"; // делаем 1 запрос

$tree = array();
for($i=0,$n=count($cats); $i<$n; $i++){
$cat = $cats[$i];
if(empty($tree[$cat->parent]))
$tree[$cat->parent]=array();
$tree[$cat->parent][] = $cat;
}
ShowCategory($tree);
// создаем рекурсивную функцию
function ShowCategory(&$tree,$k_parent=0){
if(empty($tree[$k_parent])) return;


echo $k_parent ? "<ul>" : "";
for($i=0,$n=count($tree[$k_parent]); $i<$n; $i++){
echo "<li>";
if(??????? кое условие ?????????){ // здесь суть моего вопроса
echo "<span>".$tree[$k_parent][$i]->name."</span>";
}else{
echo "<a href='index.php?category=".$tree[$k_parent][$i]->id."'>".$tree[$k_parent][$i]->p_name."</a>";
}

ShowCategory($tree, $tree[$k_parent][$i]->id);
echo "</li>";
}
echo $k_parent ? "</ul>" : "";
} ?>


Пожалуйста помогите создать условие.
поясню: в результате самый последний потомок должен быть ссылкой, любой родитель нет.
Быстрый ответ:

 Графические смайлики |  Показывать подпись
Здесь расположена полная версия этой страницы.
Invision Power Board © 2001-2024 Invision Power Services, Inc.