PHP& Mysql树导航菜单具有无限子类别

时间:2012-07-23 22:31:08

标签: php mysql arrays recursion tree

我正在寻找一种从mysql数据库中提取数据的方法,以创建无限的子类别菜单。 菜单中有大约1500个类别,它们按以下格式存储在数据库表(菜单)中:

category_id,类别标题,parent_id

我想做1个mysql查询,将所有数据放入数组中。

$menu_list =array();

$query = mysql_query("SELECT * FROM menu ORDER BY category_id ASC");

if(mysql_num_rows($query) != 0) {
        while($row = mysql_fetch_array($query)){
              $category_id = $row['category_id'];
              $title = $row['category_title'];
              $parent_id = $row[parent_id'];
              $menu_list[] = array($category_id,$title,$parent_id);
        }
}

这是我目前用于将数据转换为数组的代码。

然后我需要循环遍历数组以构建菜单。

然后我将隐藏所有子类别并使用jquery扩展(我可以这样做没有问题)

我遇到的问题是通过数组并以正确的顺序显示数据。 菜单具有无限的子类别,因此它可能需要是一个递归函数来检索数据。 我已经在这里完成了很多例子,但后来我迷失了如何显示数据..

我现在需要最终:

main item
  sub cat
  sub cat
      sub cat 
          sub cat 
main item
   sub cat
main item
main item
   sub cat
       sub cat
          sub cat
             sub cat
             sub cat

etc...  

每个类别都在自己的div中 然后我需要能够编辑每个类别(如果需要,可以编辑标题名称和位置) 然后,如果我更改子类别所在的位置,快速页面刷新将按新顺序重新加载菜单..

最终我想把它变成拖放,但这将是以后的日子。

我希望这已经足够解释了。

提前致谢..


所以这个问题又回来困扰我了...... 我的ajax调用另一个mysql select的方法对于CMS部分是可以的,因为它只是不时使用。 但现在我们已经走到了前端。 每次查看页面时,都会使用大量的mysql请求来提取菜单的前3个级别。 随着类别越来越大,现在导致1600多个类别每次被拉多次,即使每天1000次访问也会导致每天超过1000000次sql请求。 所以我认为我肯定需要将类别拉入数组,然后以递归方式遍历数组。

我已经查看了上面的解决方案,它们似乎在纸上工作,但在实践中却没有。

如果有人可以尝试给我另一种解决方案,我们将不胜感激。

回顾我的db我有:id,category_name,parent_id

我现在正在使用带有mysql的PDO和准备好的语句以增加安全性。

提前致谢..

6 个答案:

答案 0 :(得分:3)

以下是您需要的代码

category_id AS id,类别标题AS kategori和parent_id AS kid

function countsubcat($pid)
{

   $r=mysql_query("select count(kid) AS say from meskat where kid='$pid' limit 1");

   $rw=mysql_fetch_array($r);

   return $rw['say'];

}

function listmenu($pid = 0)
{

   $res = mysql_query("select id,kategori,kid from meskat where kid='$pid'");

   while($cat=mysql_fetch_array($res))

   {

     echo '<li>';

     print'<a href="#">'.$cat['kategori'].'</a>';

     if(countsubcat($cat['id'])>0)

     {

      print'<ul>';

         listmenu($cat['id']);

      print'</ul>';

     }
   echo '</li>';

   }

}

echo '<ul>';

listmenu(0); //starting from base category

echo '</ul>';`

答案 1 :(得分:0)

在MySQL中,这将需要许多查询,每个级别一个,加上至少一个。

  1. 您获取所有顶级类别,将它们用作森林(一组树)的根节点。
  2. 您抓住所有类别的子类别。
  3. 将这些类别中的每一个作为子节点添加到父节点
  4. 如果还有孩子,请转到第二步,否则停止。
  5. 你最后得到的一个树是深度优先进行转换为某些(大概)html表示(例如嵌套列表)。

    您可以执行一些优化。如果您按父ID对子级别进行排序,那么您可以假设连续的子级块,这意味着您不必执行尽可能多的查找来查找正确的父级,只需检查父级ID更改。

    如果您维护按ID编制索引的当前最低级别类别的列表,则可以加快林的创建速度。

    如果您重叠步骤2和4,那么您只进行n + 1次查询(其中n是级别数),因此检查更多孩子也会抓住下一级别的孩子。

    就内存而言,这将使用树所需的内存,加上最低级别的查找表和当前的父ID。

    构建算法相当快,可以根据类别数量线性扩展。

    此方法还成功避免了损坏的数据部分,因为它不会获取具有周期(没有根节点)的数据。

    我还建议为子查询使用一个准备好的语句,因为MySQL会编译并缓存查询,它只会通过线路发送新数据来加速操作。

    如果您不了解其中的一些概念,我建议您点击维基百科,因为我尝试使用标准术语。

答案 2 :(得分:0)

如果,您可以假设任何类别category_id > parent_id都为真,以下递归函数将菜单表示为多级数组并将其呈现为嵌套的HTML列表将起作用:

$menu = array();

$query = mysql_query("SELECT category_id, category_title, parent_id FROM menu ORDER BY category_id ASC");
if(mysql_num_rows($query) != 0) {
        while($row = mysql_fetch_assoc($query)) {
            if(is_null($row['parent_id']))
                $menu['children'][] = $row;
            else
                add_to_menu(&$menu,$row);
        }
}

function add_to_menu($menu,$item) {
    if(isset($menu['children'])) {
        foreach($menu['children'] as &$child) {
            if($item['parent_id'] == $child['category_id']) {
                $child['children'][] = $item;
            } else {
                add_to_menu(&$child,$item);
            }
        }
    }
}

function render_menu($menu) {
    if(isset($menu['children'])) {
        echo '<ul>';
        foreach($menu['children'] as &$child) {
            echo "<li>";
            echo $child['category_id']." : ".$child['category_title'];
            if(isset($child['children'])) {
                render_menu(&$child);
            }
            echo "</li>";
        }
        echo '</ul>';
    }
}

render_menu($menu);

答案 3 :(得分:0)

你可以改变表结构吗?在这种情况下,您可以查看the Nested Set Model(链接包括说明和实现详细信息,向下滚动到嵌套集模型)。节点插入和删除变得更加复杂,但允许您只在一个查询中检索整个树。

答案 4 :(得分:0)

因此,经过对不同解决方案的各种尝试,我实际上已经使用jquery和ajax加载来检索下一个级别。

我的主要类别都有1的父级 - 我对所有类别进行mysql扫描,其中1为父ID。

然后会显示主要类别。附加到每个主要类别我已添加文件夹图标,如果他们有任何子类别。 我还插入了一个id为该类别的空白div。

单击该文件夹时,它会对php脚本执行ajax调用,以查找具有该父级的所有类别。这些附加到parents_id div。

此转换添加类别(使用css填充以指示子类别),如果它具有子类别,则再次添加文件夹符号,以及使用category_id添加另一个空白div。

您可以根据客户端要求为多个类别和子类别继续此操作。

我进一步采取了一个步骤,并在类别/子类别中添加了一个编辑图标,以便移动或更改类别。

如果你想要一些示例代码,请告诉我......

答案 5 :(得分:0)

实际上,构建树只需要很少的代码。 您还只需要一个查询。 这是我提出的(我使用Drupal的数据库,但它足够清楚):

$items = db_select('menu_items', 'mi')
    ->fields('mi')
    ->orderBy('position')
    ->execute()
    ->fetchAllAssoc('id', PDO::FETCH_ASSOC);

// Example of result. The ID as key is important for this to work.
$items = array(
    3 => array('id' => 3, 'parent' => NULL, 'title' => 'Root', 'position' => 0),
    4 => array('id' => 4, 'parent' =>    3, 'title' => 'Sub',  'position' => 0),
    5 => array('id' => 5, 'parent' =>    4, 'title' => 'Sub sub', 'position' => 0),
    6 => array('id' => 6, 'parent' =>    4, 'title' => 'Sub sub', 'position' => 1),
);

// Create the nested structure. Note the & in front of $item.
foreach($items as &$item)
    if($item['parent'])
        $items[$item['parent']]['sub items'][$item['mid']] =& $item;

// Now remove the children from the root
foreach($items as $id => $item)
    if($item['parent']) // This is a child
        unset($items[$id])

此时,您只需要一个递归函数来显示菜单:

function print_menu($items) {
    echo '<ul>';
    foreach($items as $item) {
        echo '<li>';

        echo '<a href="#">' . $item['title'] . '</a>';
        if(!empty($item['sub items']))
                print_menu($item['sub items']);

        echo '</li>';
    }
    echo '</ul>';
}