無限分類是電商、CMS、後台管理系統的核心需求。本文採用「路徑編碼法」結合 MySQL CONCAT() 函數,單查詢即可取得完整層級關係,無需遞迴多次查詢,效能與簡潔度兼備。
核心欄位 catpath 以分隔符(如 -)串聯所有祖先 ID,根節點為 0:
CREATE TABLE `category` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`catpath` VARCHAR(255) DEFAULT NULL COMMENT '祖先路徑,如 0-1-3',
`name` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
範例資料:
INSERT INTO `category` VALUES (1, '0', '網站首頁'), (2, '0-1', 'Linux OS'), (3, '0-1', 'Apache服務器'), (4, '0-1', 'MySQL數據庫'), (5, '0-1', 'PHP腳本語言'), (6, '0-1-2', 'Linux 系統教程'), (7, '0-1-2', 'Linux 網絡技術'), (8, '0-1-2', 'Linux 安全基礎'), (9, '0-1-2-7', 'Linux LAMP'), (10, '0-1-3-10', 'apache Server');
分隔符可自訂(,、| 等),只需統一處理邏輯。
利用 CONCAT(catpath, '-', id) 產生完整路徑 abspath,直接 ORDER BY abspath 即可獲得樹狀順序:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', 'root');
$sql = "SELECT id, CONCAT(catpath, '-', id) AS abspath, name
FROM category
ORDER BY abspath";
$stmt = $pdo->query($sql);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
根據路徑深度(分隔符計數)計算縮排層級:
<?php
$options = '';
foreach ($rows as $row) {
$depth = substr_count($row['abspath'], '-'); // 0 為根
$indent = str_repeat(' ', $depth);
$options .= "<option value=\"{$row['id']}\">{$indent}{$row['name']}</option>";
}
echo "<select name=\"cat_id\">{$options}</select>";
輸出效果:
網站首頁
Linux OS
Linux 系統教程
Linux 網絡技術
Linux LAMP
Linux 安全基礎
Apache服務器
apache Server
MySQL數據庫
PHP腳本語言
<?php
function buildCatpath(PDO $pdo, int $parentId): string {
if ($parentId === 0) return '0';
$stmt = $pdo->prepare("SELECT catpath FROM category WHERE id = ?");
$stmt->execute([$parentId]);
$parent = $stmt->fetchColumn();
return $parent . '-' . $parentId;
}
// 使用範例
$newCatpath = buildCatpath($pdo, 2); // 0-1-2
$pdo->prepare("INSERT INTO category (catpath, name) VALUES (?, ?)")
->execute([$newCatpath, '新分類']);
| 優點 | 缺點 |
|---|---|
| 單查詢取全樹、排序自然 | catpath 長度受 VARCHAR 限制(極深層級可能溢位) |
| 移動子樹只需更新該節點及後代 catpath | 大量移動時需批次更新,可配合觸發器或佇列 |
| 相容所有 MySQL 版本,無需 CTE/遞迴 CTE | 查詢特定深度需額外計算 |
若專案使用 MySQL 8.0+,可改用 WITH RECURSIVE CTE 方案,彈性更高;但路徑編碼法在舊版本與高讀取場景下依然極具競爭力。