tags:

views:

174

answers:

9

Hi there, I have a website with articles and sections, each sections can have a parent section, as much as they like for example:

subject 1
 -subject 2 
 --subject 3
 -subject 4
 --subject 5
 --subject 6
 ---subject 7
subject 8
subject 9

etc..

Now, i want to fetch them recursively, what is the most efficient way to do it via php and mysql?

Tnx in advanced.

+5  A: 

That depends on how you have stored your data. There is a good article on MySQL.com called Managing Hierarchical Data in MySQL that talks about this.

Daniel Egeberg
i just wanted to say that. this is the only way to fetch all hierarchy in one query
Dobiatowski
A: 

the first part of the article relates only to 4 levels, the last part is not the way I want to do it.

my structure is something like that:

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | test                 |   NULL |
|           2 | subject1             |   1    |
|           3 | subject2             |   1    |
|           4 | subject3             |   2    |
|           5 | subject4             |   4    |
+-------------+----------------------+--------+

I dont want to complicate things, I want to do it in the most simple way, but to fetch the data in the most efficient way.

WEBProject
I have implemented a very similar structure to this and found it to work well.
Jon Winstanley
A: 

if assume, that your table has id, id_parrent and name fields

function tree($id)
{
    $query = "SELECT `name`,`id` from `table` WHERE `id_parrent` = '$id'";
    $result = mysql_query($query);
     if(mysql_num_rows($result) != 0)
       {
            echo "<ul>";
            while($row = mysql_fetch_array($result))
            {             
                 echo "<li>",$row[name],"</li>";
                 tree($row[id]);
            }
            echo "</ul>";
       }
}

by so you'll get the whole tree

category1
       category1_1
       category1_2
              category1_2_1
              category1_2_2
       category1_3
...........................
Syom
thats the easy part, i want to do it with one query..
WEBProject
i have only one query in my function :P
Syom
i'm joking. sorry, but i can't imagine is it possible to do with single query.
Syom
this one is the slowest solution.
Dobiatowski
@Dobiatowski don't say slowest. it will be more slower if you deside to print them one by one, by hand :D
Syom
rotfl, you shouldnt think what will be faster for you but for the CPU ;)
Dobiatowski
+1  A: 

Well, you can fetch all the categories in an array in just the one query, as you know:

$query = "SELECT `name`,`id` from `table`";

Having that in an array, you can build the tree with some nested loops. It won't be fast, but is simple, and faster than using recursive queries. Also, you can cache the built tree and not having to rebuild it every time.

Jani
i've wrote the tree function not with recursy too, but the script was very large, and doesn't work faster, then recursy, so why not recurcy?
Syom
SQL queries + recursion is something you want avoid, at least in my experience.
Jani
+1  A: 

I cant guarentee I havent made any syntax mistakes but this should work with one query.

class menuSystem{ 
var $menu;
var $db; #this variable is my db class assigned from the construct, I havent written the construct in, I can if you need it
function startNav(){
    $this->db->runQuery("select * from table order by parent asc");
    $menu = array(0 => array('children' => array()));
    while ($data = $this->db->fetchArray()) {
      $menu[$data['category_id']] = $data;
      $menu[(is_null($data['parent']) ? '0' : $data['parent'] )]['children'][] = $data['category_id'];
    }
    $this->menu = $menu;
    $nav = '<ul>';
    foreach($menu[0]['children'] as $child_id) {
      $nav .= $this->makeNav($menu[$child_id]);
    }
    $nav .= '</ul>';
}

function makeNav($menu){
   $nav_one = '<li>'."\n\t".'<a href="#">'$menu['name'].'</a>';
    if(isset($menu['children']) && !empty($menu['children'])) {
      $nav_one .= "<ul>\n";
      foreach($menu['children'] as $child_id) {
        $nav_one .= $this->makeNav($this->menu[$child_id]);
      }
      $nav_one .= "</ul>\n";
    }
    $nav_one .= "</li>\n";
return $nav_one;
}

}

EDIT: sorry I use this in my code as a class and thought I had managed to take it out of a class for you but forgot I needed $this->menu

UPDATE: I think the below is out of a class now, sorry for such a long answer

$result = mysql_query("select * from table order by parent_id asc");
$menu = array(0 => array('children' => array()));
while ($data = mysql_fetch_array($result)) {
  $menu[$data['category_id']] = $data;
  $menu[(is_null($data['parent_id']) ? '0' : $data['parent_id'] )]['children'][] = $data['category_id'];
}
$global_menu = $menu;
$nav = '<ul>';
foreach($menu[0]['children'] as $child_id) {
  $nav .= makeNav($menu[$child_id]);
}
$nav .= '</ul>';

function makeNav($menu) {
  global $global_menu;
  $nav_one = '<li>'."\n\t".'<a href="#">' . $menu['name'].'</a>';
  if(isset($menu['children']) && !empty($menu['children'])) {
    $nav_one .= "<ul>\n";
    foreach($menu['children'] as $child_id) {
      $nav_one .= makeNav($global_menu[$child_id]);
    }
    $nav_one .= "</ul>\n";
  }
  $nav_one .= "</li>\n";
  return $nav_one;
}

Hope it helps

Luke

Luke
+1  A: 

You could have a look at this topic : how to get the hierarchical menu from mysql that was opened yesterday and is about the same thing.

Serty Oan
+4  A: 

If the tree isn't too large, you can simply build the tree in PHP using some clever references.

$nodeList = array();
$tree     = array();

$query = mysql_query("SELECT category_id, name, parent FROM categories ORDER BY parent");
while($row = mysql_fetch_assoc($query)){
    $nodeList[$row['category_id']] = array_merge($row, array('children' => array()));
}
mysql_free_result($query);

foreach ($nodeList as $nodeId => &$node) {
    if (!$node['parent'] || !array_key_exists($node['parent'], $nodeList)) {
        $tree[] = &$node;
    } else {
        $nodeList[$node['parent']]['children'][] = &$node;
    }
}
unset($node);
unset($nodeList);

This will give you the tree structure in $tree with the children in the respective children-slot.

We've done this with fairly large trees ( >> 1000 items) and it's very stable and a lot faster than doing recursive queries in MySQL.

Stefan Gehrig
This is some really nice code, still trying to get my head fully around it but its very compact for its functionality, I might be stealing it :P
Luke
Syom
Stefan Gehrig
@Stefan Gehrig, I have been trying to understand your code and in doing so I removed the third reference, $nodeList[$node['parent']]['children'][] = became $nodeList[$node['parent']]['children'][] = $node; and it still worked. This hasnt helped me understand it any more :) but I thought I would mention it. Luke
Luke
Stefan Gehrig
The trick is to simply add references to `$tree` so that changes in the children collection of each node get propagated into `$tree`. Both `unset()`-calls ensure that we don't leave any references dangling around and that `$tree` now contains the real nodes (as `$tree` now holds the last reference to the "real" node).
Stefan Gehrig
Thanks Stefan thats perfect, I just wrote a quick test script and I understand it all, it was only how the references where affecting it that I could not grasp and now I do. thanks. :)
Luke
A: 

From your example to each category save its full path in another field:
1 - 1
2 - 1.2
3 - 1.2.3
4 - 1.4
5 - 1.4.5
6 - 1.4.6
7 - 1.4.6.7
8 - 8
9 - 9

and then just query with ORDER BY on that field

codez
A: 

I have a good solution for this problem.

It does not use recursion. And it requires a single query to the database.

I have just posted the code in my answer to a similar question here:

http://stackoverflow.com/questions/2871861#3368622

Thanks.

J. Bruni