Все рано или поздно сталкивались с древовидной структурой данных из SQL. Я уверен, что каждого волнует вопрос какое решение создания древовидной структуры наиболее оптимальнее и менее ресурсоемко. Я в этом вопросе не исключение и хотелось бы узнать ваши варианты решения задачи.
После напишу какое решение придумал я. (чтобы не позориться(PS: хотя как знать... я считаю, что оно оптимальнее тех, что я нарыл в рунете))
Спустя 42 минуты, 34 секунды (15.04.2011 - 00:48) kirik написал(а):
Спустя 32 минуты, 46 секунд (15.04.2011 - 01:21) Snus написал(а):
kirik
Я читал ту документацию.
Мое решение схоже с твоим :)
DUMP
PHP
Я читал ту документацию.
Мое решение схоже с твоим :)
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 считал, считаю и надеюсь буду считать самым великим извращением какое я когда-либо встречал. (хотя лучше них в некоторых ситуациях решения не найти)
Собственно вот функция его для построения массива: может кому пригодиться:
Получать категории и программно строить древовидный массив, и если дерево большое просто кешировать. Способы с 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 - содержит собственно массив например
В аргументы idName, $pidName, мы передадим ('id','pid') т.к. в базе колонки так называются
[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
Есть легкие вариации каждого из них (к примеру добавление номера уровня в структуру) , но основа именно такая.
списки смежности логично применять там, где требуются минимальные накладные расходы по добавлению, удалению и переносу узлов дерева.
вложенные подмножества логично применять там, где добавление, удаление и перенос узлов - достаточно редкие операции, а самым быстрым и безболезненным нужен вывод дерева в порядке иерархии (просмотр в глубину)
метод материализованного пути как правило хорош там, где уже есть соответствующие по построению ключи прикладного уровня.
Остальное, как правило, комбинации вышеперичисленного, и применяются крайне редко. Поскольку комбинируются эти методы со скрипом.
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;
}
Взято отсюда.
Подскажите, как организовать, чтобы "потомки" выстраивались в обратном порядке. Т.е. все элементы уже выстраиваются в обратном порядке сортировкой из базы, а нужно, чтобы "потомки" выстраивались в нормальном порядке, как на картинке.
Спустя 2 часа, 11 минут, 48 секунд (23.04.2011 - 18:35) sergeiss написал(а):
Пропустил темку... Я выкладывал своё решение для Postgre http://phpforum.ru/index.php?showtopic=31806&hl=
На MySQL не думал, как сделать. Поэтому не знаю. Но мне кажется, что я бы сделал функцию в MySQL, внутри которой уже делал бы всё, что нужно. Там можно было бы сделать параметры для изменения порядка сортировки, ограничения выводимого уровня вложенности и т.д. и т.п. Из ПХП вызывался бы один только запрос.
На MySQL не думал, как сделать. Поэтому не знаю. Но мне кажется, что я бы сделал функцию в MySQL, внутри которой уже делал бы всё, что нужно. Там можно было бы сделать параметры для изменения порядка сортировки, ограничения выводимого уровня вложенности и т.д. и т.п. Из ПХП вызывался бы один только запрос.
Спустя 3 часа, 36 минут, 53 секунды (23.04.2011 - 22:12) ИНСИ написал(а):
Цитата |
я бы сделал функцию в MySQL, внутри которой уже делал бы всё, что нужно |
Очень было бы интересно посмотреть на такой вариант ......
Snus - Решил сетевой пирамидой заняться
Спустя 19 минут, 31 секунда (23.04.2011 - 22:32) sergeiss написал(а):
Цитата (velbox @ 23.04.2011 - 23:12) |
Очень было бы интересно посмотреть на такой вариант ...... |
Ключевое слово у меня было "бы" Не хочу глубоко "втыкать" в 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 |
выводим данные из БД
Пожалуйста помогите создать условие.
поясню: в результате самый последний потомок должен быть ссылкой, любой родитель нет.
есть значения в базе:
| 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>" : "";
} ?>
Пожалуйста помогите создать условие.
поясню: в результате самый последний потомок должен быть ссылкой, любой родитель нет.