【问题标题】:Recursive mysql select?递归mysql选择?
【发布时间】:2013-01-17 11:28:56
【问题描述】:

我看到了this answer,我希望他是不正确的,就像有人错误地告诉主键在一个列上,而我不能在多个列上设置它。

这是我的桌子

create table Users(id INT primary key AUTO_INCREMENT,
    parent INT,
    name TEXT NOT NULL,
    FOREIGN KEY(parent)
    REFERENCES Users(id)
);


+----+--------+---------+
| id | parent | name    |
+----+--------+---------+
|  1 |   NULL | root    |
|  2 |      1 | one     |
|  3 |      1 | 1down   |
|  4 |      2 | one_a   |
|  5 |      4 | one_a_b |
+----+--------+---------+

我想选择用户 id 2 并递归,以便我得到它的所有直接和间接子项(所以 id 4 和 5)。

我如何以这样的方式编写它?我在 postgresql 和 sqlserver 中看到了递归。

【问题讨论】:

  • 比尔·卡尔文是正确的。 MySQL 没有任何递归查询函数,就像 SQL Server 一样,因为它有 CTE。但递归的行为仍然可以模拟。 :D
  • 我不认为你可以通过单个查询在 MySQL 中进行递归,但我已经通过存储过程完成了类似的父层次结构查询,这些过程一直在寻找父级别,直到找不到更多父条目。 . 但是通过完成后删除的临时表完成......这对你有用吗?
  • @DRapp:这可能是可以接受的。无论哪种方式学习都会很有趣

标签: mysql recursion


【解决方案1】:

我知道上面可能有更好和更有效的答案,但是这个 sn-p 提供了一种稍微不同的方法,并提供了 - 祖先和孩子。

这个想法是不断地将相关的rowIds插入到临时表中,然后获取一行来寻找它的亲戚,冲洗重复直到所有的行都被处理完。查询可以优化为仅使用 1 个临时表。

这是一个有效的sqlfiddle 示例。

 CREATE TABLE Users
        (`id` int, `parent` int,`name` VARCHAR(10))//
    
    INSERT INTO Users
        (`id`, `parent`, `name`)
    VALUES  
        (1, NULL, 'root'),
        (2, 1, 'one'),
        (3, 1, '1down'),
        (4, 2, 'one_a'),
        (5, 4, 'one_a_b')//
    
    CREATE PROCEDURE getAncestors (in ParRowId int) 
    BEGIN 
       DECLARE tmp_parentId int;
       CREATE TEMPORARY TABLE tmp (parentId INT NOT NULL);
       CREATE TEMPORARY TABLE results (parentId INT NOT NULL);
       INSERT INTO tmp SELECT ParRowId;
       WHILE (SELECT COUNT(*) FROM tmp) > 0 DO
         SET tmp_parentId = (SELECT MIN(parentId) FROM tmp);
         DELETE FROM tmp WHERE parentId = tmp_parentId;
         INSERT INTO results SELECT parent FROM Users WHERE id = tmp_parentId AND parent IS NOT NULL;
         INSERT INTO tmp SELECT parent FROM Users WHERE id = tmp_parentId AND parent IS NOT NULL;
       END WHILE;
       SELECT * FROM Users WHERE id IN (SELECT * FROM results);
    END//
    
    CREATE PROCEDURE getChildren (in ParRowId int) 
    BEGIN 
       DECLARE tmp_childId int;
       CREATE TEMPORARY TABLE tmp (childId INT NOT NULL);
       CREATE TEMPORARY TABLE results (childId INT NOT NULL);
       INSERT INTO tmp SELECT ParRowId;
       WHILE (SELECT COUNT(*) FROM tmp) > 0 DO
         SET tmp_childId = (SELECT MIN(childId) FROM tmp);
         DELETE FROM tmp WHERE childId = tmp_childId;
         INSERT INTO results SELECT id FROM Users WHERE parent = tmp_childId;
         INSERT INTO tmp SELECT id FROM Users WHERE parent = tmp_childId;
       END WHILE;
       SELECT * FROM Users WHERE id IN (SELECT * FROM results);
    END//

用法:

CALL getChildren(2);
                
    -- returns 
    id  parent  name
    4   2   one_a
    5   4   one_a_b


CALL getAncestors(5);
                
    -- returns 
    id  parent  name
    1   (null)  root
    2   1   one
    4   2   one_a

【讨论】:

    【解决方案2】:
    CREATE DEFINER = 'root'@'localhost'
    PROCEDURE test.GetHierarchyUsers(IN StartKey INT)
    BEGIN
      -- prepare a hierarchy level variable 
      SET @hierlevel := 00000;
    
      -- prepare a variable for total rows so we know when no more rows found
      SET @lastRowCount := 0;
    
      -- pre-drop temp table
      DROP TABLE IF EXISTS MyHierarchy;
    
      -- now, create it as the first level you want... 
      -- ie: a specific top level of all "no parent" entries
      -- or parameterize the function and ask for a specific "ID".
      -- add extra column as flag for next set of ID's to load into this.
      CREATE TABLE MyHierarchy AS
      SELECT U.ID
           , U.Parent
           , U.`name`
           , 00 AS IDHierLevel
           , 00 AS AlreadyProcessed
      FROM
        Users U
      WHERE
        U.ID = StartKey;
    
      -- how many rows are we starting with at this tier level
      -- START the cycle, only IF we found rows...
      SET @lastRowCount := FOUND_ROWS();
    
      -- we need to have a "key" for updates to be applied against, 
      -- otherwise our UPDATE statement will nag about an unsafe update command
      CREATE INDEX MyHier_Idx1 ON MyHierarchy (IDHierLevel);
    
    
      -- NOW, keep cycling through until we get no more records
      WHILE @lastRowCount > 0
      DO
    
        UPDATE MyHierarchy
        SET
          AlreadyProcessed = 1
        WHERE
          IDHierLevel = @hierLevel;
    
        -- NOW, load in all entries found from full-set NOT already processed
        INSERT INTO MyHierarchy
        SELECT DISTINCT U.ID
                      , U.Parent
                      , U.`name`
                      , @hierLevel + 1 AS IDHierLevel
                      , 0 AS AlreadyProcessed
        FROM
          MyHierarchy mh
        JOIN Users U
        ON mh.Parent = U.ID
        WHERE
          mh.IDHierLevel = @hierLevel;
    
        -- preserve latest count of records accounted for from above query
        -- now, how many acrual rows DID we insert from the select query
        SET @lastRowCount := ROW_COUNT();
    
    
        -- only mark the LOWER level we just joined against as processed,
        -- and NOT the new records we just inserted
        UPDATE MyHierarchy
        SET
          AlreadyProcessed = 1
        WHERE
          IDHierLevel = @hierLevel;
    
        -- now, update the hierarchy level
        SET @hierLevel := @hierLevel + 1;
    
      END WHILE;
    
    
      -- return the final set now
      SELECT *
      FROM
        MyHierarchy;
    
    -- and we can clean-up after the query of data has been selected / returned.
    --    drop table if exists MyHierarchy;
    
    
    END
    

    它可能看起来很麻烦,但是要使用它,请这样做

    call GetHierarchyUsers( 5 );
    

    (或任何您想查找 UP 层次树的键 ID)。

    前提是从您正在使用的一个 KEY 开始。然后,以此为基础再次加入 users 表,但基于第一个条目的 PARENT ID。找到后,更新临时表,以免在下一个周期再次尝试加入该键。然后继续,直到找不到更多的“父”ID 密钥。

    无论嵌套有多深,这都会将整个记录层次结构返回到父级。但是,如果您只想要 FINAL 父级,则可以使用 @hierlevel 变量仅返回文件中添加的最新一个,或 ORDER BY 和 LIMIT 1

    【讨论】:

    • 这是一个很好的解决方案,但是如果在第一次运行完成之前第二次运行查询,则临时 MyHeirarchy 表将被删除,第一次查询将失败。创建名称中带有时间戳的临时表并在过程结束时(而不是开始)将其删除将解决该问题。
    • @andrewlorien,是的,这是真的,但是你正在处理动态 sql。另一种选择是使用每个连接和/或用户唯一的#tempTable 名称(或##temp)表。这样可以防止从一个用户意外删除到另一个用户。
    猜你喜欢
    • 2023-04-10
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    相关资源
    最近更新 更多