採用 type_p_id 字段存儲「父級路徑 + 當前 ID」的逗號分隔字符串,如 1,3,7, 表示:根節點 1 → 子節點 3 → 當前節點 7。查詢子樹只需 LIKE '1,3,7,%',極其高效。
CREATE TABLE `types` (
`type_id` INT(11) NOT NULL AUTO_INCREMENT,
`type_name` VARCHAR(20) NOT NULL,
`type_p_id` VARCHAR(64) NOT NULL DEFAULT '-',
PRIMARY KEY (`type_id`),
KEY `type_p_id` (`type_p_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
<?php
class CategoryManager {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/** 獲取所有分類,按層級排序 */
public function getAll(): array {
$stmt = $this->pdo->query("SELECT * FROM types ORDER BY type_p_id");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/** 添加分類 */
public function add(string $name, int $parentId = 0): bool {
$this->pdo->beginTransaction();
try {
$this->pdo->exec("INSERT INTO types (type_name) VALUES ({$this->pdo->quote($name)})");
$id = (int)$this->pdo->lastInsertId();
if ($parentId === 0) {
$path = "$id,";
} else {
$stmt = $this->pdo->prepare("SELECT type_p_id FROM types WHERE type_id = ?");
$stmt->execute([$parentId]);
$parentPath = $stmt->fetchColumn() ?? '';
$path = $parentPath . "$id,";
}
$this->pdo->prepare("UPDATE types SET type_p_id = ? WHERE type_id = ?")
->execute([$path, $id]);
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
return false;
}
}
/** 遞歸刪除分類及所有子分類 */
public function delete(int $id): bool {
return (bool)$this->pdo->exec(
"DELETE FROM types WHERE type_p_id LIKE " . $this->pdo->quote("$id,%")
);
}
/** 修改分類名稱 */
public function rename(int $id, string $newName): bool {
return (bool)$this->pdo->prepare("UPDATE types SET type_name = ? WHERE type_id = ?")
->execute([$newName, $id]);
}
/** 獲取樹狀結構數組(用於前端渲染) */
public function getTree(): array {
$list = $this->getAll();
$tree = [];
foreach ($list as $item) {
$level = substr_count($item['type_p_id'], ',') - 1;
$item['level'] = max(0, $level);
$tree[] = $item;
}
return $tree;
}
}
?>
level 字段計算縮進:str_repeat(' ', $level * 4)substr_count($path, ',') == 1 表示無子節點)| 優點 | 缺點 |
|---|---|
| 查詢子樹極快(LIKE 前綴匹配) | 路徑長度受 VARCHAR(64) 限制,層級過深會截斷 |
| 移動子樹只需更新 path 前綴 | 需應用層保證 path 一致性,無外鍵約束 |
| 無需遞歸查詢,單條 SQL 搞定 | 不支援超過 64 字符的深層路徑 |
路徑存儲法適合「讀多寫少、層級固定、需快速獲取子樹」的場景,是中小型項目的首選方案。