MySQL Closure Table 分层数据库 - 如何以正确的顺序提取信息
Posted
技术标签:
【中文标题】MySQL Closure Table 分层数据库 - 如何以正确的顺序提取信息【英文标题】:MySQL Closure Table hierarchical database - How to pull information out in the correct order 【发布时间】:2011-11-24 04:45:54 【问题描述】:我有一个 mysql 数据库,它使用 Closure Table 方法保存分层数据。一个简单的示例数据库创建脚本遵循问题。我目前的问题是如何以正确的顺序从数据库中提取数据?我目前正在使用以下 select 语句。
SELECT `TreeData`.`iD`, `TreeData`.`subsectionOf`,
CONCAT(REPEAT('-', `TreePaths`.`len`),`TreeData`.`name`),
`TreePaths`.`len`,`TreePaths`.`ancestor`,`TreePaths`.`descendant`
FROM `TreeData`
LEFT JOIN `TreePaths` ON `TreeData`.`iD` = `TreePaths`.`descendant`
WHERE `TreePaths`.`ancestor` = 1
ORDER BY `TreeData`.`subsectionOrder`
它提取了正确的信息,但顺序不正确。
带有示例数据的示例数据库创建脚本。
-- Simple Sample
SET FOREIGN_KEY_CHECKS=0;
DROP TRIGGER IF EXISTS Tree_Insert;
DROP TRIGGER IF EXISTS Tree_Update;
DROP TABLE IF EXISTS TreePaths;
DROP TABLE IF EXISTS TreeData;
SET FOREIGN_KEY_CHECKS=1;
CREATE TABLE `TreeData` (
`iD` INT NOT NULL, -- PK
`subsectionOf` INT, -- Parent ID & FK
`subsectionOrder` INT, -- Oder of Subsections
`name` NVARCHAR(500) NOT NULL, -- Name for the entry
PRIMARY KEY (`iD`),
FOREIGN KEY (`subsectionOf`) REFERENCES TreeData(`iD`) ON DELETE CASCADE,
INDEX(`name`)
) ENGINE = MYISAM;
-- Trigger to update the EntryPaths table for new entries
DELIMITER //
CREATE TRIGGER `Tree_Insert` AFTER INSERT ON `TreeData` FOR EACH ROW
BEGIN
INSERT INTO `TreePaths` (`ancestor`, `descendant`, `len`)
SELECT `ancestor`, NEW.`iD`, len + 1 FROM `TreePaths`
WHERE `descendant` = NEW.`subsectionOf`
UNION ALL SELECT NEW.`iD`, NEW.`iD`, 0;
END; //
DELIMITER ;
DELIMITER //
CREATE TRIGGER `Tree_Update` BEFORE UPDATE ON `TreeData` FOR EACH ROW
BEGIN
-- From http://www.mysqlperformanceblog.com/2011/02/14/moving-subtrees-in-closure-table/
IF OLD.`subsectionOf` != NEW.`subsectionOf` THEN
-- Remove the node from its current parent
DELETE a FROM `TreePaths` AS a
JOIN `TreePaths` AS d ON a.`descendant` = d.`descendant`
LEFT JOIN `TreePaths` AS x
ON x.`ancestor` = d.`ancestor` AND x.`descendant` = a.`ancestor`
WHERE d.`ancestor` = OLD.`iD` AND x.`ancestor` IS NULL;
-- Add the node to its new parent
INSERT `TreePaths` (`ancestor`, `descendant`, `len`)
SELECT supertree.`ancestor`, subtree.`descendant`, supertree.`len`+subtree.`len`+1
FROM `TreePaths` AS supertree JOIN `TreePaths` AS subtree
WHERE subtree.`ancestor` = OLD.`iD`
AND supertree.`descendant` = NEW.`subsectionOf`;
END IF;
END; //
DELIMITER ;
CREATE TABLE `TreePaths` (
`ancestor` INT NOT NULL,
`descendant` INT NOT NULL,
`len` INT NOT NULL,
PRIMARY KEY (`ancestor`, `descendant`),
FOREIGN KEY (`ancestor`) REFERENCES TreeData(`iD`) ON DELETE CASCADE,
FOREIGN KEY (`descendant`) REFERENCES TreeData(`iD`) ON DELETE CASCADE
) ENGINE = MYISAM;
INSERT INTO `TreeData` VALUES(1, NULL, NULL, 'Root A');
INSERT INTO `TreeData` VALUES(2, 1, 1, 'Item 1');
INSERT INTO `TreeData` VALUES(3, 1, 2, 'Item 2');
INSERT INTO `TreeData` VALUES(4, 1, 3, 'Item 3');
INSERT INTO `TreeData` VALUES(5, 2, 2, 'Item 1 Sub Item 2');
INSERT INTO `TreeData` VALUES(6, 2, 1, 'Item 1 Sub Item 1');
INSERT INTO `TreeData` VALUES(7, 1, 3, 'Item 4');
INSERT INTO `TreeData` VALUES(8, 4, 1, 'Item 3 Sub Item 1');
INSERT INTO `TreeData` VALUES(9, 4, 2, 'Item 3 Sub Item 2');
INSERT INTO `TreeData` VALUES(10, NULL, NULL, 'Root B');
INSERT INTO `TreeData` VALUES(11, 10, 1, 'Item A');
INSERT INTO `TreeData` VALUES(12, 10, 2, 'Item B');
INSERT INTO `TreeData` VALUES(13, 10, 3, 'Item C');
【问题讨论】:
【参考方案1】:SELECT d.`iD`, d.`subsectionOf`,
CONCAT(REPEAT('-', p.`len`), d.`name`) as hier,
p.`len`, p.`ancestor`, p.`descendant`,
GROUP_CONCAT(crumbs.`ancestor`) AS breadcrumbs
FROM `TreeData` AS d
JOIN `TreePaths` AS p ON d.`iD` = p.`descendant`
JOIN `TreePaths` AS crumbs ON crumbs.`descendant` = p.`descendant`
WHERE p.`ancestor` = 1
GROUP BY d.`iD`
ORDER BY breadcrumbs;
+----+--------------+---------------------+-----+----------+------------+-------------+
| iD | subsectionOf | hier | len | ancestor | descendant | breadcrumbs |
+----+--------------+---------------------+-----+----------+------------+-------------+
| 1 | NULL | Root A | 0 | 1 | 1 | 1 |
| 2 | 1 | -Item 1 | 1 | 1 | 2 | 1,2 |
| 5 | 2 | --Item 1 Sub Item 2 | 2 | 1 | 5 | 1,2,5 |
| 6 | 2 | --Item 1 Sub Item 1 | 2 | 1 | 6 | 1,2,6 |
| 3 | 1 | -Item 2 | 1 | 1 | 3 | 1,3 |
| 4 | 1 | -Item 3 | 1 | 1 | 4 | 1,4 |
| 8 | 4 | --Item 3 Sub Item 1 | 2 | 1 | 8 | 1,4,8 |
| 9 | 4 | --Item 3 Sub Item 2 | 2 | 1 | 9 | 1,4,9 |
| 7 | 1 | -Item 4 | 1 | 1 | 7 | 1,7 |
+----+--------------+---------------------+-----+----------+------------+-------------+
【讨论】:
这仅适用于您的树是按顺序创建的。想象一下,您的根项目不是 1,而是 12,这将不再有效。在此处移动几个节点以删除序列,此打印将中断。有人有其他解决方案吗? 如果您的闭包表还包含pathlength
列,您可以使用GROUP_CONCAT(crumbs.ancestor ORDER BY pathlength)
。
@Thomas GROUP_CONCAT(DISTINCT crumbs.ancestor
ORDER BY crumbs.ancestor
) 也会完成这项工作
我不明白为什么,但我必须 ORDER BY pathlength DESC
才能使面包屑的顺序正确(即以祖先的 ID 开头)。
我得到了这个工作,但是如果我有多个根项(根 A、根 B、根 C,...)并且我想要从根开始的完整树,我该如何实现项目。根项目的 subsectionOf 为 NULL以上是关于MySQL Closure Table 分层数据库 - 如何以正确的顺序提取信息的主要内容,如果未能解决你的问题,请参考以下文章
R语言对dataframe(data.table)数据分层随机抽样实战