如何在MySQL中执行递归SELECT查询?


79

我得到一张下表:

col1 | col2 | col3
-----+------+-------
1    | a    | 5
5    | d    | 3
3    | k    | 7
6    | o    | 2
2    | 0    | 8

如果用户搜索“ 1”,程序将查看col1具有“ 1”的,然后它将在col3“ 5”中得到一个值,然后程序将继续在其中搜索“ 5”,col1并且将得到“ 3”在中col3,依此类推。因此它将打印出:

1   | a   | 5
5   | d   | 3
3   | k   | 7

如果用户搜索“ 6”,它将打印出:

6   | o   | 2
2   | 0   | 8

如何建立一个SELECT查询来做到这一点?


在这篇文章中有一个针对您问题的解决方案stackoverflow.com/questions/14658378/recursive-mysql-select
麦地那

Answers:


70

编辑

@leftclickben提到的解决方案也是有效的。我们也可以对它使用存储过程。

CREATE PROCEDURE get_tree(IN id int)
 BEGIN
 DECLARE child_id int;
 DECLARE prev_id int;
 SET prev_id = id;
 SET child_id=0;
 SELECT col3 into child_id 
 FROM table1 WHERE col1=id ;
 create TEMPORARY  table IF NOT EXISTS temp_table as (select * from table1 where 1=0);
 truncate table temp_table;
 WHILE child_id <> 0 DO
   insert into temp_table select * from table1 WHERE col1=prev_id;
   SET prev_id = child_id;
   SET child_id=0;
   SELECT col3 into child_id
   FROM TABLE1 WHERE col1=prev_id;
 END WHILE;
 select * from temp_table;
 END //

我们使用临时表存储输出结果,并且由于临时表是基于会话的,因此不会有关于输出数据不正确的任何问题。

SQL FIDDLE Demo

试试这个查询:

SELECT 
    col1, col2, @pv := col3 as 'col3' 
FROM 
    table1
JOIN 
    (SELECT @pv := 1) tmp
WHERE 
    col1 = @pv

SQL FIDDLE Demo

| COL1 | COL2 | COL3 |
+------+------+------+
|    1 |    a |    5 |
|    5 |    d |    3 |
|    3 |    k |    7 |

注释
parent_id值应小child_id于此解决方案的。


2
People Pls将此答案标记为最佳解决方案,因为类似问题的其他一些解决方案(关于mysql中的递归选择)非常复杂,因为它需要创建表并将数据插入其中。这个解决方案非常优雅。

5
只需注意他的解决方案,就没有循环类型依赖性,它就会进入无限循环,而另一件事它只会找到1个该col3类型的记录,因此,如果有多个记录,那么它将无法工作。
2013年

2
@HamidSarfraz现在可以使用sqlfiddle.com/#!2/74f457/14了。这将为您工作。随着顺序搜索的进行,id将始终比父级具有更大的值,因为首先需要创建父级。请告知您是否需要任何其他详细信息。
Meherzad 2015年

5
这不是解决方案。这只是表扫描的幸运副作用。请仔细阅读@leftclickben的答案,否则您将像我一样浪费很多时间。
jaeheung 2015年

2
Tum我知道递归SQL是如何工作的。MySQL尚未实现递归CTE,因此您提供的链接中的一个可行选择是(使用存储过程/函数)。另一个是使用mysql变量。但是,这里的答案不是优雅,而是相反,简直太可怕了。它没有显示递归SQL。如果在您的情况下有效,则@jaehung正确指出,这只是偶然。而且我不介意可怕的答案。我只是投票给他们。但是,我想,在+50时,这是一个可怕的答案。
ypercubeᵀᴹ

51

@Meherzad接受的答案仅在数据按特定顺序排列时有效。它恰好适用于OP问题中的数据。就我而言,我必须对其进行修改以使用我的数据。

注意仅当每个记录的“ id”(问题中的col1)的值大于该记录的“父ID”(问题中的col3)时,此方法才有效。这是常见的情况,因为通常需要先创建父对象。但是,如果您的应用程序允许更改层次结构(其中某个项目可能在其他地方重新生成父级),那么您将不能依靠它。

这是我的查询,以防它对某人有所帮助;请注意,它不适用于给定的问题,因为数据未遵循上述所需的结构。

select t.col1, t.col2, @pv := t.col3 col3
from (select * from table1 order by col1 desc) t
join (select @pv := 1) tmp
where t.col1 = @pv

区别table1在于排序方式是col1使父项紧随其后(因为父项的col1值低于子项的值)。


没错,如果一个孩子有2个父母,那么它可能不会两者都选
Tum

谢啦。Teamworek在这篇文章中做到了!当我更改@pv的值时,它可以正常工作。那正是我一直在寻找的东西。
Mohamed Ennahdi El Idrissi 2015年

如果我想将其用作较大选择中每一行的父ID的group_concat列,那该怎么办(这意味着@pv变量的值对于每一行都是动态的)。子查询中的join不知道master列(我尝试连接到该列),使用另一个变量也不起作用(总是返回NULL)
qdev

我创建了一个自定义函数,该函数使用group_concat生成树路径,现在我可以将每行的列值作为参数发送;)
qdev 2015年

您如何看待我发布的新答案?并不是说你的不是很好,但是我只想拥有一个可以支持父ID>子ID的SELECT。
DJon大师16年

18

leftclickben答案对我有用,但是我想要一个从给定节点到树的路径再到根的路径,而这些路径似乎正沿着树的另一方向走。因此,为了清楚起见,我不得不翻转一些字段并重命名,如果其他人也想要的话,这对我来说是有用的-

item | parent
-------------
1    | null
2    | 1
3    | 1
4    | 2
5    | 4
6    | 3

select t.item_id as item, @pv:=t.parent as parent
from (select * from item_tree order by item_id desc) t
join
(select @pv:=6)tmp
where t.item_id=@pv;

给出:

item | parent
-------------
6    | 3
3    | 1
1    | null

@ BoB3K,如果ID不一定是“ order”,则可以正常工作。如果沿链的父母身份证比其子女的身份证更高,这似乎不起作用?例如,链1> 120 > 112仅返回((112,120)),而链2> 22> 221返回完整链((221,22),(22,2),(2,null))
Tomer Cagan

已经有一段时间了,但是我想我记得在原始答案中读到,如果项目ID的顺序不正确,这是行不通的,如果ID是自动递增键,通常就不成问题。
BoB3K

它运作良好,我将其用于我的网站...这里的问题是无法订购结果ASC。1 3 6我用array_reverse()php代替.....任何sql解决方案?

8

存储过程是最好的方法。因为Meherzad的解决方案仅在数据遵循相同顺序时才有效。

如果我们有这样的表结构

col1 | col2 | col3
-----+------+------
 3   | k    | 7
 5   | d    | 3
 1   | a    | 5
 6   | o    | 2
 2   | 0    | 8

它不会工作。 SQL Fiddle Demo

这是实现相同目的的示例过程代码。

delimiter //
CREATE PROCEDURE chainReaction 
(
    in inputNo int
) 
BEGIN 
    declare final_id int default NULL;
    SELECT col3 
    INTO final_id 
    FROM table1
    WHERE col1 = inputNo;
    IF( final_id is not null) THEN
        INSERT INTO results(SELECT col1, col2, col3 FROM table1 WHERE col1 = inputNo);
        CALL chainReaction(final_id);   
    end if;
END//
delimiter ;

call chainReaction(1);
SELECT * FROM results;
DROP TABLE if exists results;

这是一个可靠的解决方案,我在使用时没有遇到任何麻烦。您是否可以在其他方向上帮助我,例如,沿着树-我找到了父ID == inputNo的所有行,但是许多ID可能有一个父ID。
2015年

8

如果您希望能够进行SELECT而不会出现父ID必须低于子ID的问题,则可以使用一个函数。它也支持多个子代(就像一棵树一样),并且该树可以具有多个头。如果数据中存在循环,还可以确保中断。

我想使用动态SQL来传递表/列名称,但是MySQL中的函数不支持此功能。

DELIMITER $$

CREATE FUNCTION `isSubElement`(pParentId INT, pId INT) RETURNS int(11)
DETERMINISTIC    
READS SQL DATA
BEGIN
DECLARE isChild,curId,curParent,lastParent int;
SET isChild = 0;
SET curId = pId;
SET curParent = -1;
SET lastParent = -2;

WHILE lastParent <> curParent AND curParent <> 0 AND curId <> -1 AND curParent <> pId AND isChild = 0 DO
    SET lastParent = curParent;
    SELECT ParentId from `test` where id=curId limit 1 into curParent;

    IF curParent = pParentId THEN
        SET isChild = 1;
    END IF;
    SET curId = curParent;
END WHILE;

RETURN isChild;
END$$

在这里,必须将表test修改为真实的表名,并且可能必须针对您的真实名调整列(ParentId,Id)。

用法:

SET @wantedSubTreeId = 3;
SELECT * FROM test WHERE isSubElement(@wantedSubTreeId,id) = 1 OR ID = @wantedSubTreeId;

结果:

3   7   k
5   3   d
9   3   f
1   5   a

用于创建测试的SQL:

CREATE TABLE IF NOT EXISTS `test` (
  `Id` int(11) NOT NULL,
  `ParentId` int(11) DEFAULT NULL,
  `Name` varchar(300) NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

insert into test (id, parentid, name) values(3,7,'k');
insert into test (id, parentid, name) values(5,3,'d');
insert into test (id, parentid, name) values(9,3,'f');
insert into test (id, parentid, name) values(1,5,'a');
insert into test (id, parentid, name) values(6,2,'o');
insert into test (id, parentid, name) values(2,8,'c');

编辑:这是一个小提琴,自己进行测试。它迫使我使用预定义的分隔符来更改分隔符,但是它可以工作。


0

在DJon大师的基础上建立

这是简化的函数,它提供了返回深度的附加实用程序(如果您想使用逻辑来包含父任务或在特定深度进行搜索)

DELIMITER $$
FUNCTION `childDepth`(pParentId INT, pId INT) RETURNS int(11)
    READS SQL DATA
    DETERMINISTIC
BEGIN
DECLARE depth,curId int;
SET depth = 0;
SET curId = pId;

WHILE curId IS not null AND curId <> pParentId DO
    SELECT ParentId from test where id=curId limit 1 into curId;
    SET depth = depth + 1;
END WHILE;

IF curId IS NULL THEN
    set depth = -1;
END IF;

RETURN depth;
END$$

用法:

select * from test where childDepth(1, id) <> -1;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.