表 category 存儲無限級分類 (id, parent_id, name)。需按**層級順序**輸出:父節點在前,子節點緊跟其後,同級按 ID 排序。
CREATE TABLE category (
id INT(10) PRIMARY KEY,
parent_id INT(10) DEFAULT 0,
name VARCHAR(50)
);
INSERT INTO category VALUES
(1, 0, 'pizza'), -- 根
(2, 0, 'burger'), -- 根
(3, 0, 'coffee'), -- 根
(4, 1, 'piperoni'), -- 1.1
(5, 1, 'cheese'), -- 1.2
(6, 1, 'vegetariana'), -- 1.3
(7, 5, 'extra cheese');-- 1.2.1
思路:為每個節點生成「祖先 ID 路徑」字符串,如 1.5.7,按字符串排序即得樹形順序。
getPriorityDELIMITER $$
CREATE FUNCTION getPriority (inID INT)
RETURNS VARCHAR(255)
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE gParentID INT DEFAULT 0;
DECLARE gPriority VARCHAR(255) DEFAULT '';
SET gPriority = CAST(inID AS CHAR); -- 起始為自身 ID
SELECT parent_id INTO gParentID FROM category WHERE id = inID;
WHILE gParentID > 0 DO -- 0 為根節點標識
SET gPriority = CONCAT(gParentID, '.', gPriority);
SELECT parent_id INTO gParentID FROM category WHERE id = gParentID;
END WHILE;
RETURN gPriority;
END$$
DELIMITER ;
SELECT id, parent_id, name, getPriority(id) AS path_code FROM category ORDER BY path_code;
| id | parent_id | name | path_code | 層級 |
|---|---|---|---|---|
| 1 | 0 | pizza | 1 | 1 |
| 4 | 1 | piperoni | 1.4 | 1.1 |
| 5 | 1 | cheese | 1.5 | 1.2 |
| 7 | 5 | extra cheese | 1.5.7 | 1.2.1 |
| 6 | 1 | vegetariana | 1.6 | 1.3 |
| 2 | 0 | burger | 2 | 2 |
| 3 | 0 | coffee | 3 | 3 |
| 限制 | 影響 | 應對 |
|---|---|---|
| 單線程遞歸查表 | 大數據量 (萬級+) 極慢 | **應用層遞歸 / 閉包表 / 物化路徑** 替代 |
| 依賴單根節點 (parent_id=0) | 多棵樹 (森林) 需修改判斷條件 | WHERE parent_id IS NULL 或指定根 ID 集合 |
| 字符串排序陷阱 | 1.10 < 1.2 (字典序) | ID 固定寬度補零 LPAD(id, 6, '0') 或用 CAST(... AS UNSIGNED) 分段排序 |
| 不支持循環引用檢測 | 臟數據導致死循環 | 表級觸發器/約束防止循環,或函數加深度限制 |
ALTER TABLE category ADD COLUMN path VARCHAR(500) NOT NULL DEFAULT ''; -- 插入時維護:new_path = parent.path + '.' + new_id -- 查詢:ORDER BY path -- 祖先查詢:WHERE path LIKE '1.5.%' -- 子孫查詢:WHERE path = '1.5' OR path LIKE '1.5.%'
CREATE TABLE category_closure (
ancestor INT NOT NULL,
descendant INT NOT NULL,
depth INT NOT NULL,
PRIMARY KEY (ancestor, descendant)
);
-- 所有祖先-後代關係預計算,含自身 depth=0
-- 查詢所有子孫:SELECT descendant FROM category_closure WHERE ancestor=5 AND depth>0
WITH RECURSIVE cte AS (
SELECT id, parent_id, name, CAST(id AS CHAR(255)) AS path, 1 AS level
FROM category WHERE parent_id = 0
UNION ALL
SELECT c.id, c.parent_id, c.name, CONCAT(cte.path, '.', c.id), cte.level + 1
FROM category c JOIN cte ON c.parent_id = cte.id
)
SELECT * FROM cte ORDER BY path;
getPriority 函數適合**小數據量、臨時查詢、不想改表結構**的場景。