Как вывести дерево из базы?

M2
На сайте с 11.01.2011
Offline
341
1620

Собственно, наверное, самая распространённая проблема...

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

Итак, что есть: есть таблица разделов и подразделов. Никаких left_id, right_id и тому подобных вещей нет. В разделах есть ID, и название. В подразделах - ID, ID родительского раздела, название подраздела.

Что требуется:

Вывести дерево разделов и подразделов. Вложенность только до 2 уровня. Т.е. картинка примерно такая:


--Раздел 1
----Подраздел 1 раздела 1
----Подраздел 2 раздела 1
--Раздел 2
----Подраздел 1 раздела 2
--Раздел 3
--Раздел 4
--Раздел 5
----Подраздел 1 раздела 5
----Подраздел 2 раздела 5
----Подраздел 3 раздела 5

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

Всем заранее спасибо за помощь :)

------------------- Крутые VPS и дедики. Качество по разумной цене ( http://cp.inferno.name/view.php?product=1212&gid=1 ) VPS25OFF - скидка 25% на первый платеж по ссылке выше
Anamnado
На сайте с 08.02.2010
Offline
242
#1

первое поле [ID ключ] - идентификатор записи (элемента), последнее поле внешний ключ [V_ID] - показывает родителя элемента - если не знаешь что такое внешний ключ - почитай - рассказывать долго

имееем

1 - папка1 - 0

2 - файл - 1

3 - файл - 1

4 - папка2 - 0

5 - файл - 4

6 - файл - 1

7 - файл - 4

и так далее

если внешний ключ не задан или равен ноль (3я колонка) это корневая папка - эт сам придумаешь как лучше

это толчок а не прям разжовывание.... (неограниченную вложенность тоже сам додумаешь ....)

Ragnarok
На сайте с 25.06.2010
Offline
239
#2

что мешает сделать вложенный цикл

mysql_query("SELECT * from table_разделы WHERE id_родителя=$i");

for($i=0;$i<count(массив_разделов_из_базы_разделов);$i++){

echo "Раздел ".$i."<br />";

mysql_query("SELECT * from table_подразделов WHERE id_родителя=$i");

for($j=0;$j<count(массив_записей_из_предыдущего_mysql_query);$j++){

echo "подраздел ".$j;

}

}

типа такого

//TODO: перестать откладывать на потом
T
На сайте с 20.03.2007
Offline
67
Toy
#3
Ragnarok:
что мешает сделать вложенный цикл
mysql_query("SELECT * from table_разделы WHERE id_родителя=$i");
for($i=0;$i<count(массив_разделов_из_базы_разделов);$i++){
echo "Раздел ".$i."<br />";
mysql_query("SELECT * from table_подразделов WHERE id_родителя=$i");
for($j=0;$j<count(массив_записей_из_предыдущего_mysql_query);$j++){
echo "подраздел ".$j;
}
}

типа такого

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

А вообще для php есть библиотеки для работы с деревьями в бд. Сам не использую, но видел не один раз.

В гугле искать Adjacency List, Nested Sets

M2
На сайте с 11.01.2011
Offline
341
#4

В общем вывел сам. Пока ещё не вывел, просто сформировал требуемую структуру в php. Кому интересно - выкладываю код и пояснения.


$cs = array();

$db->query('SELECT cat_id, cat_name FROM pages_categories');
while ($data = $db->parse_query('array'))
{
$cs[][$data['cat_id']] = $data['cat_name'];
}


foreach ($cs as $key => $value)
{
foreach ($value as $key1 => $value1)
{
settype($cs[$key][$key1], 'array');
$cs[$key][$key1]['name'] = array();
$query = mysql_query('SELECT * FROM pages_subcategories WHERE parent_cat_id = ' . $key1);
$data = array();
$rows = mysql_num_rows($query);

if ($rows > 0)
{
while ($subcat_data = mysql_fetch_array($query))
{
$cs[$key][$key1]['name']['subcat_id'] = $subcat_data['subcat_id'];
$cs[$key][$key1]['name']['subcat_name'] = $subcat_data['subcat_name'];
$cs[$key][$key1]['name']['parent_cat_id'] = $subcat_data['parent_cat_id'];
}
}
}
}

Кратко о том, что здесь происходит.

$cs - это общий массив, где всё хранится. Сначала первым циклом while выводим все разделы и помещаем их в массив. Получаем двумерный массив.

Далее, поскольку массив двумерный, у нас идёт двойной цикл foreach (поскольку $value является массивом, который также необходимо обработать).

Создаём ключ массива name, который также преобразуем в массив. И по условию наличия подразделов ($rows > 0) выводим соответственно разделам. Получается на первый взгляд не очень удобоваримая структура, но smarty с ней справится :))

Ragnarok

Отвечаю на ваше сообщение. Так делать мне нельзя, поскольку у меня всё основано на шаблонах, и очень не хочется html код переводить в файлы php.

Вот что получилось в результате:


Array
(
[0] => Array
(
[1] => Array
(
[0] => Первый раздел
[name] => Array
(
[subcat_id] => 5
[subcat_name] => Ещё один подраздел в первом разделе
[parent_cat_id] => 1
)

)

)

[1] => Array
(
[2] => Array
(
[0] => Второй раздел
[name] => Array
(
[subcat_id] => 6
[subcat_name] => Ещё один подраздел вo втором разделе
[parent_cat_id] => 2
)

)

)

[2] => Array
(
[3] => Array
(
[0] => asafasf
[name] => Array
(
)

)

)

[3] => Array
(
[4] => Array
(
[0] => asf
[name] => Array
(
)

)

)

[4] => Array
(
[5] => Array
(
[0] => 111www
[name] => Array
(
)

)

)

[5] => Array
(
[6] => Array
(
[0] => Новый раздел без описания
[name] => Array
(
)

)

)

[6] => Array
(
[7] => Array
(
[0] => Новый раздел с описанием
[name] => Array
(
)

)

)

)
C
На сайте с 28.01.2010
Offline
70
#5

ЭЭ, вас количество запросов в подобном цикле не смущает?

Не проще сделать нечто вроде


SELECT
subcat_id, subcat_name, parent_cat_id, cat_id, cat_name
FROM
pages_subcategories
INNER JOIN
pages_categories
ON
(pages_categories.cat_id = pages_subcategories.parent_cat_id)

?

Это - личная подпись. Здесь обычно ставят ссылки на всякие кривые сайты, надеясь получить "жирный бек".
M2
На сайте с 11.01.2011
Offline
341
#6

Counselor

Мне всё это дело потом распарсивать в Smarty.

Ко всем:

Вот Smarty-код, который корректно обрабатывает это дело, выводит в виде нормальной таблички.


<table width="70%">

<tr>
<td><b>Раздел / Подраздел</b></td>
<td><b>Действия</b></td>
</tr>
{foreach $cs as $key => $value}
{foreach $value as $key1 => $value1}
{foreach $value1 as $key2 => $value2}

{if !is_array($value2)}
{foreach $value2 as $key3 => $value3}
<tr>
<td>{$value2}</td>
<td>
<a href="#" onClick="delete_category({$key1})" title="Удалить раздел"><img src="../images/delete.png"></a>&nbsp;&nbsp;
<a href="#" onClick="rename_category({$key1})" title="Переименовать раздел"><img src="../images/edit.png"></a></td>
</tr>
{/foreach}
{else}
{foreach $value2 as $key3 => $value3}

<tr>
<td style="padding-left: 2%;">{$value2[$key3]["subcat_name"]}</td>
<td style="padding-left: 2%;">
<a href="#" onClick="delete_subcategory({$value2[$key3]["subcat_id"]})" title="Удалить подраздел"><img src="../images/delete.png"></a>&nbsp;&nbsp;
<a href="#" onClick="rename_subcategory({$value2[$key3]["subcat_id"]})" title="Переименовать подраздел"><img src="../images/edit.png"></a></td>
</tr>
{/foreach}
{/if}
{/foreach}
{/foreach}
{/foreach}

</table>
C
На сайте с 28.01.2010
Offline
70
#7
mark2011:
Counselor
Мне всё это дело потом распарсивать в Smarty.

Какая разница куда?

запрос:


SELECT
subcat_id, subcat_name, parent_cat_id, cat_id, cat_name
FROM
pages_subcategories
INNER JOIN
pages_categories
ON
(pages_categories.cat_id = pages_subcategories.parent_cat_id)
ORDER BY parent_cat_id

сбор данных:


$data = array();
while($row = mysql_fetch_row($query)) {
$current_id = $row['parent_cat_id'];
$data[$current_id]['name'] = $row['cat_name'];
$data[$current_id]['subcategories'][] = $row['subcat_name'];
}

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


foreach ($data as $cat_name) {
echo 'Категория: '.$cat_name['name'].'<br />';
foreach ($cat_name['subcategories'] as $sub_category) {
echo 'Подкатегория: '.$sub_category.'<br />';
}
}

ps: писал прям тут в редакторе, могут быть ошибки в коде, но думаю суть ясна.

M2
На сайте с 11.01.2011
Offline
341
#8

Вот на такую конструкцию:


$data[$current_id]['subcategories'][] = $row['subcat_name'];

неоднократно получал Fatal error: cannot use [] for reading

C
На сайте с 28.01.2010
Offline
70
#9
mark2011:
Вот на такую конструкцию:

$data[$current_id]['subcategories'][] = $row['subcat_name'];


неоднократно получал Fatal error: cannot use [] for reading

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

Проверил у себя:


<?php
for ($i = 0; $i< 10;$i++) {

$data[10]['subcategories'][] = $i;
}
print_r($data);
?>


Array
(
[10] => Array
(
[subcategories] => Array
(
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
[6] => 6
[7] => 7
[8] => 8
[9] => 9
)

)

)
Sano000
На сайте с 12.04.2009
Offline
54
#10

Кстати такие узкие места можно обойти просто кешем. Первый раз создаем хтмл код как угодно, хоть даже большим количеством запросов, потом толкаем этот код в БД, в поле типа blob, так же нужно как минимум еще время, когда кеш необходимо обновить. И все, получаем кеш и время, если еще действителен, просто выводим его. Конечно еще нужно предусмотреть сброс кеша, ну это уже мелочи. Получается даже быстрее, чем длинный запрос к БД, а потом его обработка через foreach.

Программирование было хобби - стало серьезной работой

Авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий