如何在 MySQL 中进行递归 SELECT 查询?
Posted
技术标签:
【中文标题】如何在 MySQL 中进行递归 SELECT 查询?【英文标题】:How to do the Recursive SELECT query in MySQL? 【发布时间】:2013-05-13 00:31:35 【问题描述】:我得到了下表:
col1 | col2 | col3
-----+------+-------
1 | a | 5
5 | d | 3
3 | k | 7
6 | o | 2
2 | 0 | 8
如果用户搜索“1”,程序将查看具有“1”的col1
,然后它会在col3
中得到一个值“5”,然后程序将继续搜索“5” " 在col1
中,它将在col3
中得到“3”,依此类推。所以它会打印出来:
1 | a | 5
5 | d | 3
3 | k | 7
如果用户搜索“6”,它将打印出来:
6 | o | 2
2 | 0 | 8
如何构建一个SELECT
查询来做到这一点?
【问题讨论】:
在这篇文章***.com/questions/14658378/recursive-mysql-select中有解决您的问题的方法 【参考方案1】:编辑
@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
,此解决方案才能正常工作。
【讨论】:
人们请将此答案标记为最佳解决方案,因为类似问题的其他一些解决方案(关于 mysql 中的递归选择)非常复杂,因为它需要创建一个表并将数据插入其中。这个解决方案非常优雅。 只要注意他的解决方案,没有循环类型依赖,那么它将进入无限循环,另外一件事它只会找到col3
类型的 1 条记录,所以如果有多个记录然后它不会工作。
@HamidSarfraz 现在可以使用 sqlfiddle.com/#!2/74f457/14。这对你有用。因为它用于顺序搜索,并且 id 总是比 parent 具有更大的价值,因为需要首先创建 parent。如果您需要任何额外的详细信息,请告知。
这不是解决方案。这只是表扫描的幸运副作用。仔细阅读@leftclickben 的答案,否则你会像我一样浪费很多时间。
我知道递归 SQL 是如何工作的。 MySQL 尚未实现递归 CTE,因此一个可行的选项是您提供的链接中的选项(使用存储过程/函数)。另一个是使用 mysql 变量。然而,这里的答案并不优雅,而是相反,简直太可怕了。它没有显示递归 SQL。如果它适用于您的情况,那只是偶然,正如@jaehung 正确指出的那样。而且我不介意可怕的答案。我只是对他们投反对票。但我确实介意 +50 时的可怕答案。【参考方案2】:
@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
值低于子级的值)。
【讨论】:
你对,如果一个孩子有两个父母,那么它可能不会同时选择两个 谢谢伙计。 Teamwork 在这篇文章中做了它的事!当我更改@pv 的值时,我让它工作了。这正是我想要的。 如果我想将它用作更大选择中每一行的父 ID 的 group_concat 列怎么办(这意味着@pv 变量的值对于每一行都是动态的)。子查询中的联接不知道主列(我尝试连接到该列),使用另一个变量它也不起作用(总是返回 NULL) 我创建了一个自定义函数,它使用 group_concat 生成树路径,现在我可以将每行的列值作为参数发送;) 您对我发布的新答案有何看法?并不是说你的不好,但我只想有一个可以支持父 ID > 子 ID 的 SELECT。【参考方案3】:leftclickben answer 为我工作,但我想要一条从给定节点返回树到根的路径,而这些路径似乎是相反的,沿着树向下。所以,为了清楚起见,我不得不翻转一些字段并重命名,这对我有用,以防其他人也想要--
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 不一定按“顺序”排列,这是否可行。如果父母在链上的 id 高于其孩子,这似乎不起作用?例如。链 1 > 120 > 112 只会返回 ((112, 120)) 而 2 > 22 > 221 返回完整的链 ((221,22),(22,2),(2,null )) 已经有一段时间了,但我想我记得在原始答案中读到,如果项目 id 不按顺序排列,这将不起作用,如果 id 是自动增量,这通常不是问题键。 它工作得很好,我将它用于我的网站......这里的问题是无法订购结果 ASC。1 3 6
我在 php 中使用 array_reverse()
代替.....有任何 sql 解决方案吗?【参考方案4】:
存储过程是最好的方法。因为只有当数据遵循相同的顺序时,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。【参考方案5】:如果您希望能够在没有父 ID 必须低于子 ID 的问题的情况下进行 SELECT,则可以使用函数。它还支持多个孩子(就像树应该做的那样)并且树可以有多个头。如果数据中存在循环,它还可以确保中断。
我想使用动态 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');
编辑:这是一个 fiddle 自己测试它。它迫使我使用预定义的分隔符更改分隔符,但它有效。
【讨论】:
【参考方案6】:以大师 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;
【讨论】:
以上是关于如何在 MySQL 中进行递归 SELECT 查询?的主要内容,如果未能解决你的问题,请参考以下文章