80

我有一个下表:

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查询来做到这一点?

4

6 回答 6

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于此解决方案的工作。

于 2013-05-13T03:21:46.323 回答
52

@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值低于子级的值)。

于 2014-07-23T04:30:48.480 回答
20

leftclickben answer worked for me, but I wanted a path from a given node back up the tree to the root, and these seemed to be going the other way, down the tree. So, I had to flip some of the fields around and renamed for clarity, and this works for me, in case this is what anyone else wants too--

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

and

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;

gives:

item | parent
-------------
6    | 3
3    | 1
1    | null
于 2015-09-25T05:16:12.593 回答
9

存储过程是最好的方法。因为只有当数据遵循相同的顺序时,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;
于 2014-01-27T10:37:52.920 回答
9

如果您希望能够在没有父 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');

编辑:这是一个自己测试的小提琴。它迫使我使用预定义的分隔符更改分隔符,但它有效。

于 2016-06-23T13:23:20.990 回答
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;
于 2018-12-03T23:11:48.080 回答