递归mysql选择?

时间:2013-02-02 03:49:06

标签: mysql recursion

我看到了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中看到了递归。

2 个答案:

答案 0 :(得分:14)

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 );

(或者你想要找到的任何密钥ID UP层次树)。

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

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

答案 1 :(得分:4)

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

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

这是一个有效的sqlfiddle示例。

 CREATE TABLE Users
        (`id` int, `parent` int,`name` VARCHAR(10))//

    INSERT INTO Users
        (`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