处理层次结构是关系数据库的经典应用。示例:目录、树状菜单、组织结构。
问题的历史: 数据库是表格模型。对于树状数据,有几种方法,每种都有其优缺点。
问题: 标准的parent_id模型导致递归SELECT变慢。实际任务(查找所有后代、路径、子树计数)需要优化。
解决方案:
物化路径的代码示例(PostgreSQL):
CREATE TABLE categories ( id SERIAL PRIMARY KEY, name TEXT, path TEXT ); -- 示例路径存储:'1/2/5/'(根/子类别/当前) SELECT * FROM categories WHERE path LIKE '1/2/%'; -- 所有后代 2
嵌套集合的代码示例:
CREATE TABLE nested_categories ( id SERIAL PRIMARY KEY, name TEXT, lft INT NOT NULL, rgt INT NOT NULL ); -- 子节点 SELECT * FROM nested_categories WHERE lft > 2 AND rgt < 15;
关键特点:
可以简单使用带有parent_id的嵌套SELECT按任意深度查找所有后代吗?
这只适用于浅层查询。递归搜索需要递归CTE (WITH RECURSIVE) 或更复杂的方案,因为简单的JOIN会导致巨大的调用次数和低性能。
示例:
WITH RECURSIVE cte AS ( SELECT id, parent_id, name FROM categories WHERE id = 1 UNION ALL SELECT c.id, c.parent_id, c.name FROM categories c INNER JOIN cte ON c.parent_id = cte.id ) SELECT * FROM cte;
为什么分子树(嵌套集合)快速提取子树,但添加/删除节点慢?
在更改树时,很多行的lft/rgt需要同时更改(更改步骤是所有比插入/删除大的值)。对于读取来说,方法是理想的,但频繁更改时请使用其他方法。
何时使用闭包表而不是parent_id或物化路径?
闭包表非常适合对任意层级的后代进行多次查询和定期重新计算关系。缺点是需要更多空间。
示例:
CREATE TABLE closure ( ancestor INT, descendant INT, depth INT );
在网络商店中,类别通过parent_id实现。所有嵌套项手动设置,子类别的查找通过嵌套SELECT进行。
优点:
缺点:
使用物化路径或闭包表。对嵌套类别的所有查询都是瞬间完成,重新计算使用批量脚本。
优点:
缺点: