【问题标题】:PHP & Mysql tree navigation menu with unlimited sub categories具有无限子类别的 PHP & Mysql 树导航菜单
【发布时间】:2012-07-23 22:31:08
【问题描述】:

我正在寻找一种从 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 选择的方法对于 CMS 部分来说是可以的,因为它只是不时使用。 但是现在我们已经到了前端。 每次查看页面时,都会使用大量 mysql 请求来拉取菜单的前 3 级。 随着类别越来越大,这导致 1600 多个类别被多次提取,即使每天访问 1000 次也会导致每天超过 1000000 个 sql 请求。 所以我想我肯定需要将类别拉到一个数组中,然后递归地遍历数组。

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

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

只是回顾一下我的数据库:id、category_name、parent_id

我现在将 PDO 与 mysql 和准备好的语句一起使用以增加安全性..

提前谢谢..

【问题讨论】:

  • 我想你会在这个回复中得到你的答案:stackoverflow.com/a/10926952。它对我有用。
  • 在尝试查询未知深度的完整树时,构建树的邻接列表将是非常有问题的。您可能需要考虑嵌套集模型(插入/更新/删除更痛苦)或闭包表模型。查看围绕多个模型进行讨论的问题 - stackoverflow.com/questions/192220/…
  • 我还建议,除非您的类别结构一直在更改(例如每分钟或两分钟),否则最好缓存您的类别的数据表示以从 MySQL 卸载这项工作服务器。

标签: php mysql arrays recursion tree


【解决方案1】:

这是你需要的代码

category_id AS id ,类别标题 AS categori 和 parent_id AS child

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>';`

【讨论】:

  • 这样会产生大量的sql查询,拖慢整个过程。
【解决方案2】:

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

  1. 您获取所有顶级类别,将它们用作森林(一组树)的根节点。
  2. 您可以获取属于其中任何一个子类别的所有类别。
  3. 将每个类别作为子节点添加到父节点
  4. 如果还有孩子,则进行第二步,否则停止。

您最终会得到一棵树,您会先进行深度遍历以转换为某些(可能)html 表示形式(例如嵌套列表)。

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

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

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

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

构建算法相当快,随类别数量线性扩展。

此方法还成功地避免了损坏的数据部分,因为它不会抓取具有循环(无根节点)的数据。

我还建议对子查询使用准备好的语句,因为 MySQL 会编译和缓存查询,它会通过仅通过网络发送新数据来加快操作。

如果您不理解某些概念,我建议您访问 Wikipedia,因为我已尝试使用标准术语。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      如果您可以假设对于任何类别 category_id &gt; 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);
      

      【讨论】:

        【解决方案5】:

        所以在对不同的解决方案进行了各种尝试之后,我实际上已经使用 jquery 和 ajax 加载来检索下一个级别。

        我的主要类别都有 1 的父项 - 商店我对所有以 1 作为父 ID 的类别进行 mysql 扫描。

        这将显示主要类别。附加到每个主要类别,如果它们有任何子类别,我添加了一个文件夹图标。 我还插入了一个具有该类别 id 的空白 div。

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

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

        您可以根据客户的需要对尽可能多的类别和子类别继续此操作。

        我更进一步,为类别/子类别添加了一个编辑图标,以便可以移动或更改类别。

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

        【讨论】:

          【解决方案6】:

          实际上,构建树只需要很少的代码。 您也只需要一个查询。 这是我想出的(我使用 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>';
          }
          

          【讨论】:

            猜你喜欢
            • 2012-03-30
            • 1970-01-01
            • 1970-01-01
            • 2015-09-26
            • 2023-03-06
            • 2013-08-31
            • 1970-01-01
            • 1970-01-01
            • 2014-04-24
            相关资源
            最近更新 更多