我的组织表包含id
,parent_id
和name
列。此表中大约有5万行。只有一个顶级父母,其余都在此之下。在Oracle中,我能够使用level
伪列轻松检索特定组织的当前深度:
SELECT id, parent_id, level, name
FROM organizations
START WITH parent_id = 1
CONNECT BY PRIOR id = parent_id
我不知道在MySQL中执行上述操作的正确方法是什么。我需要在一个查询中获取整个树以及节点的深度。
StackOverflow上有很多与此有关的问题,但它们似乎都没有真正好的答案,主要是链接到可疑解决方案的博客。当然,这是以某种直截了当的方式实现的吗?
不幸的是,以任何方式修改表都不是一种选择,因此不可能使用嵌套集。
答案 0 :(得分:4)
这完全是搞笑的。我昨天在类似问题上获得了+50赏金:Using MySQL query to traverse rows to make a recursive tree
我引用了how to do this with Stored Procedures in the DBA StackExchange(2011年10月24日)
我将发布相同的存储过程以及我的DBA StackExchange答案中的示例:
获取任何给定节点的父级的代码
DELIMITER $$
DROP FUNCTION IF EXISTS `junk`.`GetParentIDByID` $$
CREATE FUNCTION `junk`.`GetParentIDByID` (GivenID INT) RETURNS INT
DETERMINISTIC
BEGIN
DECLARE rv INT;
SELECT IFNULL(parent_id,-1) INTO rv FROM
(SELECT parent_id FROM pctable WHERE id = GivenID) A;
RETURN rv;
END $$
DELIMITER ;
获取任何给定节点的Ancenstry的代码
DELIMITER $$
DROP FUNCTION IF EXISTS `junk`.`GetAncestry` $$
CREATE FUNCTION `junk`.`GetAncestry` (GivenID INT) RETURNS VARCHAR(1024)
DETERMINISTIC
BEGIN
DECLARE rv VARCHAR(1024);
DECLARE cm CHAR(1);
DECLARE ch INT;
SET rv = '';
SET cm = '';
SET ch = GivenID;
WHILE ch > 0 DO
SELECT IFNULL(parent_id,-1) INTO ch FROM
(SELECT parent_id FROM pctable WHERE id = ch) A;
IF ch > 0 THEN
SET rv = CONCAT(rv,cm,ch);
SET cm = ',';
END IF;
END WHILE;
RETURN rv;
END $$
DELIMITER ;
获取任何给定节点的族谱(或后代)的代码
DELIMITER $$
DROP FUNCTION IF EXISTS `junk`.`GetFamilyTree` $$
CREATE FUNCTION `junk`.`GetFamilyTree` (GivenID INT) RETURNS varchar(1024) CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE rv,q,queue,queue_children VARCHAR(1024);
DECLARE queue_length,front_id,pos INT;
SET rv = '';
SET queue = GivenID;
SET queue_length = 1;
WHILE queue_length > 0 DO
SET front_id = FORMAT(queue,0);
IF queue_length = 1 THEN
SET queue = '';
ELSE
SET pos = LOCATE(',',queue) + 1;
SET q = SUBSTR(queue,pos);
SET queue = q;
END IF;
SET queue_length = queue_length - 1;
SELECT IFNULL(qc,'') INTO queue_children
FROM (SELECT GROUP_CONCAT(id) qc
FROM pctable WHERE parent_id = front_id) A;
IF LENGTH(queue_children) = 0 THEN
IF LENGTH(queue) = 0 THEN
SET queue_length = 0;
END IF;
ELSE
IF LENGTH(rv) = 0 THEN
SET rv = queue_children;
ELSE
SET rv = CONCAT(rv,',',queue_children);
END IF;
IF LENGTH(queue) = 0 THEN
SET queue = queue_children;
ELSE
SET queue = CONCAT(queue,',',queue_children);
END IF;
SET queue_length = LENGTH(queue) - LENGTH(REPLACE(queue,',','')) + 1;
END IF;
END WHILE;
RETURN rv;
END $$
为了演示所有内容的执行,这里是示例数据
USE junk
DROP TABLE IF EXISTS pctable;
CREATE TABLE pctable
(
id INT NOT NULL AUTO_INCREMENT,
parent_id INT,
PRIMARY KEY (id)
) ENGINE=MyISAM;
INSERT INTO pctable (parent_id) VALUES (0);
INSERT INTO pctable (parent_id) SELECT parent_id+1 FROM pctable;
INSERT INTO pctable (parent_id) SELECT parent_id+2 FROM pctable;
INSERT INTO pctable (parent_id) SELECT parent_id+3 FROM pctable;
INSERT INTO pctable (parent_id) SELECT parent_id+4 FROM pctable;
INSERT INTO pctable (parent_id) SELECT parent_id+5 FROM pctable;
SELECT * FROM pctable;
以下是查看所有父母,祖先和家谱的查询
SELECT
id,parent_id,
GetParentIDByID(id),
GetAncestry(id),
GetFamilyTree(id)
FROM pctable;
试一试!!!
答案 1 :(得分:2)
没有CTE / Subquery Factoring的SQL中没有递归,因此在“Pure SQL”中,特别是在MySQL中实现的,任意级别深度都没有特别且绝对没有办法做到这一点。见How to transform a MSSQL CTE query to MySQL?
这就是为什么你找到的所有答案都是黑客的,以解决这个特定的引擎限制。如果您仍需要一个解决方案,可以尝试此解决方案:Generating Depth based tree from Hierarchical Data in MySQL (no CTEs)。 (当然,这个限制意味着在LangSec意义上解析查询并不奇怪,它允许各种有益的解析,并消除了一些安全问题,但根本不能帮助你。)
一堆耗尽层次结构的Lefts Join
就是你所剩下的,抱歉让他们失望并使用不好的双关语。
答案 2 :(得分:0)
Mysql不支持递归sql。当您只有1个维度时,您可以在parent_id = id上使用自己的左连接。
答案 3 :(得分:0)
@idok,如果您有一个以上的孩子具有相同的父母ID,则该函数将返回“截断不正确的双倍值”错误,我从dba找到了以下修复程序,它是由Sivakumar Natarayan提供的
WHILE queue_length > 0 DO
IF queue_length = 1 THEN
SET front_id = queue;
SET queue = '';
ELSE
SET front_id = SUBSTR(queue,1,LOCATE(',',queue)-1);
SET pos = LOCATE(',',queue) + 1;
SET q = SUBSTR(queue,pos);
SET queue = q;
END IF;