当前位置: 首页 > 面试题库 >

在闭包表分层数据结构中对子树进行排序

柳胡媚
2023-03-14
问题内容

我想请您帮我解决以 封闭表 形式存储的分层数据结构的排序问题。

我想使用这种结构来存储我的网站菜单。一切工作正常,但问题是我不知道 如何按 自定义顺序 对确切的子树
进行排序。目前,树已按照项目添加到数据库的顺序进行排序。

我的结构基于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         |
+----+------------+--------+---------+-------------+

注意事项:

  • id值应具有统一的长度,因为排序“ 1,3”,“ 1,6”和“ 1,327”可能不会给出您想要的顺序。但是将排序“ 001,003”和“ 001,006”以及“ 001,327”。因此,您需要以1000000+开始的id值,或者ZEROFILL在category_closure表中将其用于祖先和后代。
  • 在此解决方案中,显示顺序取决于类别ID的数字顺序。id值的数字顺序可能不代表您要显示树的顺序。或者,您可能希望自由地更改显示顺序,而与数字id值无关。或者,您可能希望同一类别数据出现在一棵以上的树中,每棵树的显示顺序不同。
    如果需要更大的自由度,则需要将排序顺序值与ID分开存储,解决方案将变得更加复杂。但是在大多数项目中,可以使用快捷方式,将类别ID的双重职责指定为树的显示顺序。

发表您的评论:

是的,您可以将“同级排序顺序”存储为闭合表中的另一列,然后使用该值而不是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         |
+----+------------+--------+---------+-------------+


 类似资料:
  • 问题内容: 鉴于下表 我要执行以下操作: 如果我搜索Ben,我想找到图像,如果没有图像,我想找到父母的图像,如果不存在,我想转到祖父母的图像…直到点击默认图片。 最有效的方法是什么?我知道SQL并不是真正为分层值设计的,但这是我需要做的。 干杯! 问题答案: MySQL缺少递归查询,而递归查询是标准SQL的一部分。许多其他品牌的数据库都支持此功能,包括PostgreSQL(请参阅http://ww

  • 问题内容: 第一次尝试熊猫,我试图先按照索引对数据透视表进行排序,然后再对一系列值进行排序。 到目前为止,我已经尝试过: 按索引然后按值对数据透视表进行排序的正确方法是什么? 问题答案: 这是一个可以做您想要的解决方案: 结果将如下所示: 将其作为API方法内置到熊猫中会很好。虽然不确定应该是什么样。

  • 问题内容: 如何使用树的值而不是键对树图进行排序? 问题答案: 您不能这样做,因为TreeMap的比较器仅针对键运行,例如,参见this 构造函数。 无论如何,您可以使用多个Collections,使用TreeMap(或HashMap)通过键查找元素,并具有SortedSet来迭代值。

  • 问题内容: 我在Python中有两个列表 我想对第一个列表进行排序,并使用结果对第二个列表进行排序。 换句话说,结果应为: 我知道如何分别对每个列表进行排序,但是如何使用对另一个列表进行排序所产生的索引排列来对一个列表进行排列呢? 问题答案: 施瓦兹变换

  • 我有一个从JSON解析的复杂“order”对象。在结构的深处,我有秩序日期。我只能把它提取成字符串!现在我尝试对订单列表进行排序。 我的想法是创建一个新的列表,其中数组由两个元素组成,首先是Order对象本身,其次是解析为Date对象的Date。例如new object[]{order,new Date(order.getOrderDate())}。然后按第二个元素排序,然后解析回一个列表并返回。

  • 我需要从表中选择所有的vip并按兰德排序,然后添加按日期排序的其他数据。在第一个子查询中,一切正常,但在第二个子查询中,spa_date DESC不起作用。我知道UNION子查询中的ORDER BY子句会被忽略而没有限制(但ORDER BY rand()起作用),但我需要所有查询(1+2)中的限制,而不是子查询中的限制 问题: 我需要选择spa_vip=1的所有spa_id并按RAND()排序,然