首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP + MySQL 實作無限級分類:Rank 路徑法完整解析

     編輯:PHP 相關知識

從遞迴到 Rank 路徑:PHP MySQL 無限級分類的高效實作方案

專案中常遇到分類層級不固定的需求(如商品分類、文章分類、知識庫目錄)。傳統 pid 遞迴查詢雖然直觀,但在深層級、大數據量下效能不佳。本文介紹「Rank 路徑編碼法」,以單次查詢 + 字串排序替代遞迴,大幅提升讀取效能,並簡化移動、刪除等維護操作。

一、資料表設計

CREATE TABLE `w_faqclass` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `pid` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父分類 ID',
  `xid` INT NOT NULL DEFAULT 0 COMMENT '同級排序',
  `classname` VARCHAR(100) NOT NULL COMMENT '分類名稱',
  `rank` VARCHAR(255) NOT NULL DEFAULT '/' COMMENT '層級路徑,如 /1/3/7/',
  PRIMARY KEY (`id`),
  KEY `idx_pid` (`pid`),
  KEY `idx_rank` (`rank`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Rank 編碼規則

  • 一級分類:pid=0,rank='/'
  • 二級分類:pid=一級ID,rank='/一級ID/'
  • 三級分類:pid=二級ID,rank='/一級ID/二級ID/'
  • 以此類推,rank 記錄從根到父節點的完整路徑,首尾皆為 /,便於 LIKE 比對。

二、核心函式庫

2.1 遞迴讀取並排序(建構下拉選單用)

/**
 * 遞迴取得已排序的無限級分類陣列
 * @param string $table  資料表名
 * @param int    $pid    起始父 ID
 * @param string $order  排序方式 ASC/DESC
 * @param array  $result 累積結果陣列(引用傳遞)
 * @return array
 */
function ReadClass($table, $pid, $order, &$result)
{
    $sql = "SELECT * FROM `$table` WHERE `pid` = ? ORDER BY `xid` $order";
    $stmt = $GLOBALS['pdo']->prepare($sql);
    $stmt->execute([$pid]);
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $result[] = $row;
        ReadClass($table, $row['id'], $order, $result);
    }
    return $result;
}

備註:原文使用已棄用的 mysql_* 函式,此處改用 PDO 預處理語句,防止 SQL 注入。

2.2 單值查詢工具

function SelectValue($table, $id, $selectCol, $whereCol = 'id')
{
    $sql = "SELECT `$selectCol` FROM `$table` WHERE `$whereCol` = ? LIMIT 1";
    $stmt = $GLOBALS['pdo']->prepare($sql);
    $stmt->execute([$id]);
    return $stmt->fetchColumn();
}

三、新增分類:同步建立 Rank

<?php
// 1. 取得父分類的 rank
if ($pid != 0) {
    $parentRank = SelectValue('w_faqclass', $pid, 'rank');
    $rank = $parentRank . $pid . '/';
} else {
    $rank = '/';
}

// 2. 寫入資料庫
$sql = "INSERT INTO `w_faqclass` (`pid`, `xid`, `classname`, `rank`) VALUES (?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$pid, $xid, $classname, $rank]);
?>

四、前端下拉選單:利用 Rank 產生縮排顯示

<select name="pid">
  <option value="0">主分類</option>
  <?php
  $list = ReadClass('w_faqclass', 0, 'ASC', []);
  foreach ($list as $item) {
      // 計算層級深度:rank 以 '/' 分割,陣列長度 - 2(首尾空元素)
      $depth = count(explode('/', $item['rank'])) - 2;
      $indent = str_repeat('├──', $depth);
      $selected = ($pid == $item['id']) ? ' selected' : '';
      echo "<option value=\"{$item['id']}\"$selected>$indent {$item['classname']}</option>";
  }
  ?>
</select>

五、修改分類:連帶更新所有子孫 Rank

修改父分類時,該節點及其所有子孫的 rank 皆需更新。利用 MySQL REPLACE() 函式可一次完成:

<?php
// 1. 計算新的 rank(同新增邏輯)
if ($pid != 0) {
    $parentRank = SelectValue('w_faqclass', $pid, 'rank');
    $newRank = $parentRank . $pid . '/';
} else {
    $newRank = '/';
}

// 2. 取得舊的完整路徑前綴(含自身 ID)
$oldRankPrefix = SelectValue('w_faqclass', $id, 'rank') . $id . '/';
$newRankPrefix = $newRank . $id . '/';

// 3. 批次更新自己與所有子孫
$sql = "UPDATE `w_faqclass` SET `rank` = REPLACE(`rank`, ?, ?) WHERE `rank` LIKE ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$oldRankPrefix, $newRankPrefix, $oldRankPrefix . '%']);

// 4. 更新基本欄位
$sql = "UPDATE `w_faqclass` SET `classname`=?, `xid`=?, `pid`=?, `rank`=? WHERE `id`=?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$classname, $xid, $pid, $newRankPrefix, $id]);
?>

原理:WHERE rank LIKE '/1/3/%' 精確鎖定該分支,REPLACE(rank, '/1/3/7/', '/1/5/7/') 僅替換路徑前綴,保留後綴(子孫相對路徑不變)。

六、刪除分類:防誤刪有子分類的節點

<?php
// 檢查是否有子分類
$childCount = SelectValue('w_faqclass', $id, 'COUNT(*)', 'pid');
if ($childCount > 0) {
    die('該分類下存在子分類,請先遷移或刪除子分類。');
}

// 執行刪除
$pdo->exec("DELETE FROM `w_faqclass` WHERE `id` = $id");
?>

七、查詢應用範例

  • 取得某分類的所有子孫:SELECT * FROM w_faqclass WHERE rank LIKE '/1/3/%'
  • 取得某分類的所有祖先:解析 rank 字串中的 ID,IN (1,3) 查詢。
  • 麵包屑導航:同上,按路徑順序查詢祖先。
  • 同級排序:ORDER BY xid ASC 即可。

八、優缺點總結與適用場景

優點缺點
讀取極快(單查/範圍查),無遞迴開銷修改父節點需更新整個子樹(寫入放大)
SQL 簡單,易於建立索引(rank 前綴索引)rank 欄位長度隨深度增長,極深層級可能超過 VARCHAR 限制
天然支援 LIKE 前綴查詢(子樹、祖先、路徑)移動子樹時需小心處理 rank 衝突

適用場景:讀多寫少、層級通常 ≤ 10、需頻繁輸出樹狀結構(選單、導航、報表)。
不適用場景:極深層級(>20)、高頻移動節點、需嚴格 ACID 事務的複雜遷移操作。

九、現代替代方案簡介

  • Closure Table(關係表):另建 category_closure(ancestor, descendant, depth),查詢/移動皆高效,寫入稍繁瑣,適合複雜樹結構。
  • 遞迴 CTE (MySQL 8.0+):WITH RECURSIVE cte AS (...) SELECT * FROM cte,標準 SQL 寫法,無需額外欄位,效能中等。
  • Nested Set(左/右值編碼):讀快寫慢,移動極痛,現已較少用。

十、總結

Rank 路徑法以「空間換時間」,用冗餘的路徑字串換取極致的讀取效能與查詢靈活性。在大多數 CMS、電商後台、知識庫等「讀多寫少、層級固定」的場景下,是極具性價比的選擇。配合 PDO 預處理與索引優化,可構建穩健高效的無限級分類系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved