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

时间:2013-05-13 00:31:35

标签: mysql sql query-optimization recursive-query

我有一张下表:

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

如果用户搜索“1”,程序将查看具有“1”的col1,然后它将获得col3“5”中的值,然后程序将继续在col1中搜索“5”,它将在col3中获得“3”,依此类推。所以它会打印出来:

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

如果用户搜索“6”,则会打印出来:

6   | o   | 2
2   | 0   | 8

如何构建SELECT查询来执行此操作?

6 个答案:

答案 0 :(得分:67)

修改

@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以使此解决方案有效。

答案 1 :(得分:50)

@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

区别在于table1col1排序,以便父母将在其之后(因为父级的col1值低于孩子的值)。

答案 2 :(得分:16)

leftclickben的答案对我有用,但我想从一个给定节点的路径备份树到根,这些似乎是从另一个方向,在树下。因此,为了清晰起见,我不得不翻转一些字段并重命名,这对我有用,以防其他人也想要这样做 -

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

select t.item_id as item_id, @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

答案 3 :(得分:7)

存储过程是最好的方法。因为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;

答案 4 :(得分:7)

如果您希望能够在没有父ID必须低于子ID的情况下拥有SELECT,则可以使用函数。它还支持多个子节点(如树应该做的),树可以有多个头。如果数据中存在循环,它还可以确保中断。

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

/**
 * @name: Login model
 * 
 */
class Login_model extends CI_Model
{

    function __construct() {
        parent::__construct();
        $this->load->database();
    }

    public function validate_user($data) {
        $this->db->distinct();
        $this->db->select('c.contact_id, name, email, password, level, c.statusid');
        $this->db->from('contact c');
        $this->db->join('deliveryreg d', 'c.contact_id = d.idcontact');
        $this->db->join('ClientLogin cl', 'cl.idcontact = c.contact_id');
        $this->db->where('c.statusid = 1');
        $this->db->where('email', $data['email']);
        $this->db->where('password', $data['password']);

        $query = $this->db->get();
        return $query->num_rows();
    }

    public function getSearch(){
        $this->db->distinct();
        $this->db->select(" s.id AS 'searchid',s.description as 'searchname',so.id AS 'sourceid',so.description as 'service'");
        $this->db->from('contact c');
        $this->db->join('deliveryreg d', 'c.contact_id = d.idcontact');
        $this->db->join('searchsource ss', 'ss.id = d.idsearchsource');
        $this->db->join('search s', 's.id = ss.idsearch');
        $this->db->join('source so', 'so.id = ss.idsource');
        $this->db->where('c.statusid = 1'); 
        $this->db->where('email');
        $ids = array('1','5','8');
        $this->db->where_in('so.id', $ids);
        $this->db->order_by('s.description', 'so.description');

        $query = $this->db->get(); 
        return $query->result();

    }

    function __destruct() {
        $this->db->close();
    }
}

此处,必须将表class Home extends CI_Controller { function __construct() { parent::__construct(); $this->load->model('login_model'); $this->load->database(); if(empty($this->session->userdata('c.contact_id'))) { $this->session->set_flashdata('flash_data', '<div class="alert alert-warning text-center">Ooops. You don\'t have access!</div>'); redirect('login'); } } public function index() { $contact_id = $this->session->userdata('c.contact_id'); $data['contact_id'] = $contact_id; $data['query'] = $this->login_model->getSearch($contact_id); $this->load->view('home', $data); } public function logout() { $data = ['c.contact_id', 'email']; $this->session->unset_userdata($data); redirect('login'); } } 修改为实际表名,并且可能必须根据您的真实姓名调整列(ParentId,Id)。

用法:

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

用于测试创建的SQL:

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

编辑:这是fiddle自己测试一下。它强迫我使用预定义的分隔符更改分隔符,但它可以工作。

答案 5 :(得分: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;