我想请您帮我解决以 封闭表 形式存储的分层数据结构的排序问题。
我想使用这种结构来存储我的网站菜单。一切工作正常,但问题是我不知道 如何按 自定义顺序 对确切的子树 进行排序。目前,树已按照项目添加到数据库的顺序进行排序。
我的结构基于Bill Karwin的有关闭包表的文章和其他一些文章。
这是带有一些DEMO数据的MySQL数据库结构:
-- -- Table `category` -- CREATE TABLE IF NOT EXISTS `category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8_czech_ci NOT NULL, `active` tinyint(1) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; INSERT INTO `category` (`id`, `name`, `active`) VALUES (1, 'Cat 1', 1), (2, 'Cat 2', 1), (3, 'Cat 1.1', 1), (4, 'Cat 1.1.1', 1), (5, 'Cat 2.1', 1), (6, 'Cat 1.2', 1), (7, 'Cat 1.1.2', 1); -- -- Table `category_closure` -- CREATE TABLE IF NOT EXISTS `category_closure` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `ancestor` int(11) DEFAULT NULL, `descendant` int(11) DEFAULT NULL, `depth` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_category_closure_ancestor_category_id` (`ancestor`), KEY `fk_category_closure_descendant_category_id` (`descendant`) ) ENGINE=InnoDB; INSERT INTO `category_closure` (`id`, `ancestor`, `descendant`, `depth`) VALUES (1, 1, 1, 0), (2, 2, 2, 0), (3, 3, 3, 0), (4, 1, 3, 1), (5, 4, 4, 0), (7, 3, 4, 1), (8, 1, 4, 2), (10, 6, 6, 0), (11, 1, 6, 1), (12, 7, 7, 0), (13, 3, 7, 1), (14, 1, 7, 2), (16, 5, 5, 0), (17, 2, 5, 1);
这是我对一棵树的SELECT查询:
SELECT c2.*, cc2.ancestor AS `_parent` FROM category AS c1 JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id) JOIN category AS c2 ON (cc1.descendant = c2.id) LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1) WHERE c1.id = __ROOT__ AND c1.active = 1 ORDER BY cc1.depth
对于__ROOT_ = 1的DEMO实例,查询得到:
id name active _parent 1 Cat 1 1 NULL 3 Cat 1.1 1 1 6 Cat 1.2 1 1 4 Cat 1.1.1 1 3 7 Cat 1.1.2 1 3
但是,例如,如果我需要更改Cat 1.1和Cat 1.2的顺序(根据名称或某些自定义顺序)怎么办?
我已经看到了一些面包屑解决方案(如何按面包屑排序),但是我不知道如何生成和更改它们。
这个问题不仅针对闭包表而且针对其他存储分层数据的方法也经常出现。在任何设计中都不容易。
我为Closure Table提出的解决方案涉及一个额外的联接。树中的每个节点都连接到其祖先链,就像“面包屑”类型查询一样。然后,使用GROUP_CONCAT()将面包屑折叠为逗号分隔的字符串,并按树中的深度对ID号进行排序。现在您有了一个可以用来排序的字符串。
SELECT c2.*, cc2.ancestor AS `_parent`, GROUP_CONCAT(breadcrumb.ancestor ORDER BY breadcrumb.depth DESC) AS breadcrumbs FROM category AS c1 JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id) JOIN category AS c2 ON (cc1.descendant = c2.id) LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1) JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant) WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1 GROUP BY cc1.descendant ORDER BY breadcrumbs; +----+------------+--------+---------+-------------+ | id | name | active | _parent | breadcrumbs | +----+------------+--------+---------+-------------+ | 1 | Cat 1 | 1 | NULL | 1 | | 3 | Cat 1.1 | 1 | 1 | 1,3 | | 4 | Cat 1.1.1 | 1 | 3 | 1,3,4 | | 7 | Cat 1.1.2 | 1 | 3 | 1,3,7 | | 6 | Cat 1.2 | 1 | 1 | 1,6 | +----+------------+--------+---------+-------------+
注意事项:
ZEROFILL
发表您的评论:
是的,您可以将“同级排序顺序”存储为闭合表中的另一列,然后使用该值而不是ancestor构建面包屑字符串。但是,如果这样做,最终将导致大量数据冗余。就是说,给定的祖先存储在多行中,每条路径都从其降序存储。因此,您必须为所有这些行上的同级排序顺序存储相同的值,这会导致出现异常的风险。
ancestor
另一种方法是创建另一个表,只有 一个 在树中每个不同的祖先行,并加入到该表以获得同级次序。
CREATE TABLE category_closure_order ( ancestor INT PRIMARY KEY, sibling_order SMALLINT UNSIGNED NOT NULL DEFAULT 1 ); SELECT c2.*, cc2.ancestor AS `_parent`, GROUP_CONCAT(o.sibling_order ORDER BY breadcrumb.depth DESC) AS breadcrumbs FROM category AS c1 JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id) JOIN category AS c2 ON (cc1.descendant = c2.id) LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1) JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant) JOIN category_closure_order AS o ON breadcrumb.ancestor = o.ancestor WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1 GROUP BY cc1.descendant ORDER BY breadcrumbs; +----+------------+--------+---------+-------------+ | id | name | active | _parent | breadcrumbs | +----+------------+--------+---------+-------------+ | 1 | Cat 1 | 1 | NULL | 1 | | 3 | Cat 1.1 | 1 | 1 | 1,1 | | 4 | Cat 1.1.1 | 1 | 3 | 1,1,1 | | 7 | Cat 1.1.2 | 1 | 3 | 1,1,2 | | 6 | Cat 1.2 | 1 | 1 | 1,2 | +----+------------+--------+---------+-------------+