【问题标题】:MySQL - Adjacency List Model - Get DepthMySQL - 邻接列表模型 - 获取深度
【发布时间】:2012-06-15 12:37:17
【问题描述】:

我有一个组织表,其中包含 idparent_idname 列。此表中大约有 50k 行。只有一个顶级父级,其余的都在此之下。在 Oracle 中,我可以使用 level 伪列轻松检索特定组织的当前深度:

SELECT id, parent_id, level, name
FROM organizations
START WITH parent_id = 1
CONNECT BY PRIOR id = parent_id

我不知道在 MySQL 中执行上述操作的正确方法是什么。我需要在一个查询中获取整个树以及节点的深度。

在 StackOverflow 上有很多与此有关的问题,但似乎没有一个问题有真正好的答案,主要是指向具有可疑解决方案的博客的链接。这肯定可以以某种直接的方式实现吗?

不幸的是,不能以任何方式修改表,因此嵌套集是不可能的。

【问题讨论】:

标签: mysql adjacency-list


【解决方案1】:

这太搞笑了。我昨天刚刚在一个类似的问题上获得了 +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 ;

获取任何给定节点的祖先的代码

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;

试试看!!!

【讨论】:

  • 我收到一个错误 ERROR 1292 (22007): 为 getFamilyTree 截断不正确的 DOUBLE 值:'4,5'。关于它为什么会发生的任何建议!?谢谢
  • 你的GetAncestry Function 基本上是使用LazyLoad 时JSTree 搜索功能的完美解决方案。谢谢老哥!
【解决方案2】:

在没有 CTE/子查询因子的 SQL 中没有递归,因此在“纯 SQL”中,尤其是在 MySQL 中实现的任意级别深度下,具体且绝对没有办法做到这一点。见How to transform a MSSQL CTE query to MySQL?

这就是为什么您找到的所有答案都是 hack,以绕过引擎的这个非常具体的限制。如果您仍然需要此解决方案,您可以尝试此解决方案:Generating Depth based tree from Hierarchical Data in MySQL (no CTEs)。 (当然,限制意味着查询解析起来并不奇怪,在 LangSec 的意义上,它允许进行各种有益的解析,并消除了一些安全问题,但对你没有帮助。)

你只剩下一堆耗尽层级的Lefts Joins,很抱歉让你失望并使用了一个糟糕的双关语。

【讨论】:

  • 2019年MySQL和MariaDB支持CTE
【解决方案3】:

@idok,如果您有多个孩子的相同父 ID,该函数将返回“截断不正确的 DOUBLE 值”错误,我从 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;

【讨论】:

    【解决方案4】:

    Mysql 不支持递归 sql。当您只有 1 个维度时,您可以在 parent_id = id 上使用 Left join。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-07
      • 1970-01-01
      相关资源
      最近更新 更多