如何查询MySql表以显示根及其子项。

时间:2011-11-03 18:03:58

标签: mysql sql parent-child hierarchy

UserID      UserName       ParentID      TopID
  1         abc            Null           Null
  2         edf             1             1
  3         gef             1             1
  4         huj             3             1
  5         jdi             4             1
  6         das             2             1
  7         new            Null           Null
  8         gka             7             7

TopID和ParentID来自userID

我想获取用户记录及其子记录和子记录。这里userid1是root,它的子节点是userid2和userid 3.所以如果用户id是1,我必须显示从userid 1到userid 6的所有记录,因为所有记录都是root的子节点和SUbchild。类似地,对于userid3,我必须显示userid3及其子Userid 4和Userid用户ID 4 Userid5 如果用户标识为3

输出应该是

Userid  Username
3          gef
4          huj
5          jdi

我将知道用户标识和topID,以便我如何进行查询以获得上述结果。

SELECT UserID, UserName  FROM tbl_User WHERE ParentID=3 OR UserID=3 And TopID=1;

通过上面的查询,我能够显示用户ID 3和用户ID 4我无法显示用户ID 5,其中有多种类型。需要帮忙。感谢

4 个答案:

答案 0 :(得分:5)

技术上可以使用存储过程在MySQL中执行递归分层查询。

这是适合您的情景的一个:

CREATE TABLE `user` (
  `UserID` int(16) unsigned NOT NULL,
  `UserName` varchar(32),
  `ParentID` int(16) DEFAULT NULL,
  `TopID` int(16) DEFAULT NULL,
  PRIMARY KEY (`UserID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO user VALUES (1, 'abc', NULL, NULL), (2, 'edf', 1, 1), (3, 'gef', 1, 1), 
 (4, 'huj', 3, 1), (5, 'jdi', 4, 1), (6, 'das', 2, 1), (7, 'new', NULL, NULL), 
 (8, 'gka', 7, 7);

DELIMITER $$
DROP PROCEDURE IF EXISTS `Hierarchy` $$
CREATE PROCEDURE `Hierarchy` (IN GivenID INT, IN initial INT)
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE next_id INT;

    -- CURSOR TO LOOP THROUGH RESULTS --
    DECLARE cur1 CURSOR FOR SELECT UserID FROM user WHERE ParentID = GivenID;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    -- CREATE A TEMPORARY TABLE TO HOLD RESULTS --
    IF initial=1 THEN
        -- MAKE SURE TABLE DOESN'T CONTAIN OUTDATED INFO IF IT EXISTS (USUALLY ON ERROR) --
        DROP TABLE IF EXISTS OUT_TEMP; 
        CREATE TEMPORARY TABLE OUT_TEMP (userID int, UserName varchar(32));
    END IF;

    -- ADD OURSELF TO THE TEMPORARY TABLE --
    INSERT INTO OUT_TEMP SELECT UserID, UserName FROM user WHERE UserID = GivenID;

    -- AND LOOP THROUGH THE CURSOR --
    OPEN cur1;
    read_loop: LOOP
        FETCH cur1 INTO next_id;

        -- NO ROWS FOUND, LEAVE LOOP --
        IF done THEN
        LEAVE read_loop;
        END IF;

        -- NEXT ROUND --
        CALL Hierarchy(next_id, 0);     
    END LOOP;

    CLOSE cur1;

    -- THIS IS THE INITIAL CALL, LET'S GET THE RESULTS --
    IF initial=1 THEN
    SELECT * FROM OUT_TEMP;
        -- CLEAN UP AFTER OURSELVES --
        DROP TABLE OUT_TEMP; 
    END IF;
END $$
DELIMITER ;

CALL Hierarchy(3,1);
+--------+----------+
| userID | UserName |
+--------+----------+
|      3 | gef      |
|      4 | huj      |
|      5 | jdi      |
+--------+----------+
3 rows in set (0.07 sec)

Query OK, 0 rows affected (0.07 sec)

CALL Hierarchy(1,1);
+--------+----------+
| userID | UserName |
+--------+----------+
|      1 | abc      |
|      2 | edf      |
|      6 | das      |
|      3 | gef      |
|      4 | huj      |
|      5 | jdi      |
+--------+----------+
6 rows in set (0.10 sec)

Query OK, 0 rows affected (0.10 sec)

是时候指出一些警告

  • 由于这是递归调用存储过程,因此需要增加max_sp_recursion_depth的大小,其最大值为255(默认为0)。

  • 我在具有有限测试数据(user表的10个元组)的非繁忙服务器上的结果需要0.07-0.10秒才能完成。性能是这样的,最好将递归放在应用程序层中。

  • 我没有利用您的TopID列,因此可能存在逻辑漏洞。但是这两个测试用例给了我预期的结果。

免责声明:这个例子只是为了表明可以在MySQL完成,而不是我支持它。存储过程,临时表和游标可能不是解决此问题的最佳方法。

答案 1 :(得分:2)

这不是一个非常干净的实现,但由于你只需要孩子和子孩子,这些都可能有效:

查询1:

SELECT UserID, UserName
FROM tbl_user
WHERE ParentID = 3 OR UserID = 3
UNION
SELECT UserID, UserName
FROM tbl_user
WHERE ParentID IN (SELECT UserID
FROM tbl_user
WHERE ParentID = 3);

查询2:

SELECT UserID, UserName
FROM tbl_user 
WHERE UserID = 3
OR ParentID = 3
OR ParentID IN (SELECT UserID
    FROM tbl_user
    WHERE ParentID = 3);

编辑1:或者,您可以修改表结构,以便更方便地查询特定类别的所有子项。请点击此链接,详细了解storing hierarchical data in MySQL

此外,您可能会考虑以类似树的方式分层存储您的数据,这些方式得到了很好的解释in this article

请注意,每种方法都需要在检索所需结果与添加/删除类别方面进行权衡,但我相信您会喜欢阅读。

答案 2 :(得分:1)

这是我见过的最好的文章之一,用于解释在SQL式数据库中存储树状数据的“Modified Preorder Tree Traversal”方法。

http://www.sitepoint.com/hierarchical-data-database/

MPTT的内容从第2页开始。

基本上,您为树中的每个节点存储“Left”和“Right”值,以便获得ParentA的所有子节点,您获得ParentA的Left和Right,然后

SELECT * 
FROM TableName
WHERE Left > ParentLeft 
AND Right < ParentRight

答案 3 :(得分:0)

获取所选子项的所有父项(在此示例中为user_id = 3):

         SELECT
              @parent_id AS _user_id,
              user_name,
              (
                 SELECT @parent_id := parent_id
                 FROM users
                 WHERE user_id = _user_id
              ) AS parent
           FROM (
        -- initialize variables
              SELECT
                 @parent_id := 3
              ) vars,
              users u
           WHERE @parent_id <> 0;

获取所选user_id的所有子项

SELECT ui.user_id AS 'user_id', ui.user_name AS 'user_name', parent_id,
FROM
(
  SELECT connect_by_parent(user_id) AS user_id
  FROM (
    SELECT
      @start_user := 3,
      @user_id := @start_user
  ) vars, users
  WHERE @user_id IS NOT NULL
  ) uo
JOIN users ui ON ui.user_id = uo.user_id

这需要以下功能

CREATE FUNCTION connect_by_parent(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _user_id INT;
        DECLARE _parent_id INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @user_id = NULL;

SET _parent_id = @user_id;
SET _user_id = -1;

IF @user_id IS NULL THEN
        RETURN NULL;
END IF;

LOOP
        SELECT  MIN(user_id)
        INTO    @user_id
        FROM    users
        WHERE   parent_id = _parent_id
                AND user_id > _user_id;

        IF @user_id IS NOT NULL OR _parent_id = @start_with THEN
                RETURN @user_id;
        END IF;

        SELECT  user_id, parent_id
        INTO    _user_id, _parent_id
        FROM    users
        WHERE   user_id = _parent_id;
END LOOP;

END

SET _parent_id = @user_id; SET _user_id = -1; IF @user_id IS NULL THEN RETURN NULL; END IF; LOOP SELECT MIN(user_id) INTO @user_id FROM users WHERE parent_id = _parent_id AND user_id > _user_id; IF @user_id IS NOT NULL OR _parent_id = @start_with THEN RETURN @user_id; END IF; SELECT user_id, parent_id INTO _user_id, _parent_id FROM users WHERE user_id = _parent_id; END LOOP;

这个例子大量使用许多sql用户可能不熟悉的会话变量,所以这里有一个可以提供一些见解的链接:session variables