MySQL递归树搜索

时间:2011-04-20 05:29:53

标签: php mysql recursive-query

我有一个数据库,其名称树可以总共下降到9级,我需要能够从分支上的任何一点向下搜索树的信号分支。

数据库:

+----------------------+
| id |  name  | parent |
+----------------------+
| 1  |  tom   |   0    |
| 2  |  bob   |   0    |
| 3  |  fred  |   1    |
| 4  |  tim   |   2    |
| 5  |  leo   |   4    |
| 6  |  sam   |   4    |
| 7  |  joe   |   6    |
| 8  |  jay   |   3    |
| 9  |  jim   |   5    |
+----------------------+

tom
 fred
  jay
bob
 tim
  sam
   joe
  leo
   jim

例如:

如果我从用户“bob”搜索“j”,我应该只获得“joe”和“jim”。如果我搜索“j”形式的“leo”我应该只得到“jim”。

我想不出任何简单的方法,所以任何帮助都表示赞赏。

6 个答案:

答案 0 :(得分:8)

您应该考虑使用Modified Preorder Tree Traversal,这样可以更轻松地进行此类查询。这是用MPTT表达的表格。我离开了父字段,因为它使一些查询更容易。

+----------------------+-----+------+
| id |  name  | parent | lft | rght |
+----------------------+-----+------+
| 1  |  tom   |   0    |  1  |   6  |
| 2  |  bob   |   0    |  7  |  18  |
| 3  |  fred  |   1    |  2  |   5  |
| 4  |  tim   |   2    |  8  |  17  |
| 5  |  leo   |   4    | 12  |  15  |
| 6  |  sam   |   4    |  9  |  16  |
| 7  |  joe   |   6    | 10  |  11  |
| 8  |  jay   |   3    |  3  |   4  | 
| 9  |  jim   |   5    | 13  |  14  |
+----------------------+-----+------+

要从用户j搜索bob,您需要使用lft的{​​{1}}和rght值:

bob

实现更新SELECT * FROM table WHERE name LIKE 'j%' AND lft > 7 AND rght < 18 lft以添加,删除和重新排序节点的逻辑可能是一个挑战(提示:如果可以,请使用现有库),但查询将是轻而易举的。

答案 1 :(得分:1)

没有一种好的/简单的方法可以做到这一点;数据库不支持树型数据结构。

您需要逐级工作以修剪从子到父的结果,或者创建一个视图,该视图给出来自给定节点的所有9代,并使用后代上的OR进行匹配。 / p>

答案 2 :(得分:1)

您是否考虑过使用递归循环?我使用一个循环来建立一个cms,我建立在codeigniter之上,允许我从站点树的任何地方开始,然后随后过滤所有的孩子&gt;大孩子&gt;伟大的孩子等等。另外,它使sql保持简短快速查询,反对许多复杂的连接。在你的情况下可能需要一些修改,但我认为它可以工作。

/**
 * build_site_tree
 *
 * @return void
 * @author Mike Waites
**/
public function build_site_tree($parent_id)
{
    return $this->find_children($parent_id);
}

/** end build_site_tree **/

// -----------------------------------------------------------------------

/**
 * find_children
 * Recursive loop to find parent=>child relationships
 *
 * @return array $children
 * @author Mike Waites
**/
public function find_children($parent_id)
{
    $this->benchmark->mark('find_children_start');

    if(!class_exists('Account_model'))
            $this->load->model('Account_model');

    $children = $this->Account_model->get_children($parent_id);

    /** Recursively Loop over the results to build the site tree **/
    foreach($children as $key => $child)
    {
        $childs = $this->find_children($child['id']);

        if (count($childs) > 0)
                $children[$key]['children'] = $childs;
    }

    return $children;

    $this->benchmark->mark('find_children_end');
}

/** end find_children **/

正如你所看到的,这是一个相当简单的版本,请记住这已经内置到codeigniter中,所以你需要将它调整到套件,但基本上我们有一个循环,每次调用自己添加到一个数组。只要你拥有parent_id avaialble,这将允许你获得整棵树,甚至从树中的一个点开始!

希望这有帮助

答案 3 :(得分:1)

新的“recursive with”构造将完成这项工作,但我不知道id MySQL支持它(还)。

with recursive bobs(id) as (
   select id from t where name = 'bob'
   union all
   select t.id from t, bobs where t.parent_id = bobs.id
)
select t.name from t, bobs where t.id = bobs.id
and name like 'j%'

答案 4 :(得分:0)

没有单一的SQL查询会以树格式返回数据 - 您需要处理以正确的顺序遍历它。

一种方法是查询MySQL以返回MPTT:

SELECT * FROM table ORDER BY parent asc;

树的根将是表的第一项,其子项将是下一个,等等,树被列为“广度优先”(层数越来越深)

然后使用PHP处理数据,将其转换为保存数据结构的对象。

或者,您可以实现给定节点的MySQL搜索函数,递归搜索并返回其所有后代的表或其所有祖先的表。由于这些过程往往很慢(递归,返回过多的数据然后被其他条件过滤),如果您知道不是一次又一次地查询这类数据,或者如果您知道数据集仍然很小(9层深,有多宽?)

答案 5 :(得分:0)

您可以使用存储过程执行此操作,如下所示:

示例调用

mysql> call names_hier(1, 'a');
+----+----------+--------+-------------+-------+
| id | emp_name | parent | parent_name | depth |
+----+----------+--------+-------------+-------+
|  2 | ali      |      1 | f00         |     1 |
|  8 | anna     |      6 | keira       |     4 |
+----+----------+--------+-------------+-------+
2 rows in set (0.00 sec)

mysql> call names_hier(3, 'k');
+----+----------+--------+-------------+-------+
| id | emp_name | parent | parent_name | depth |
+----+----------+--------+-------------+-------+
|  6 | keira    |      5 | eva         |     2 |
+----+----------+--------+-------------+-------+
1 row in set (0.00 sec)

$sqlCmd = sprintf("call names_hier(%d,'%s')", $id, $name);  // dont forget to escape $name
$result = $db->query($sqlCmd);

完整脚本

drop table if exists names;
create table names
(
id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent smallint unsigned null,
key (parent)
)
engine = innodb;

insert into names (name, parent) values
('f00',null), 
  ('ali',1), 
  ('megan',1), 
      ('jessica',3), 
      ('eva',3), 
         ('keira',5), 
            ('mandy',6), 
            ('anna',6);

drop procedure if exists names_hier;

delimiter #

create procedure names_hier
(
in p_id smallint unsigned,
in p_name varchar(255)
)
begin

declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);

set p_name = trim(replace(p_name,'%',''));

create temporary table hier(
 parent smallint unsigned, 
 id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select parent, id, v_dpth from names where id = p_id;

/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

create temporary table tmp engine=memory select * from hier;

while not v_done do

    if exists( select 1 from names n inner join tmp on n.parent = tmp.id and tmp.depth = v_dpth) then

        insert into hier select n.parent, n.id, v_dpth + 1 
            from names n inner join tmp on n.parent = tmp.id and tmp.depth = v_dpth;

        set v_dpth = v_dpth + 1;            

        truncate table tmp;
        insert into tmp select * from hier where depth = v_dpth;

    else
        set v_done = 1;
    end if;

end while;


select 
 n.id,
 n.name as emp_name,
 p.id as parent,
 p.name as parent_name,
 hier.depth
from 
 hier
inner join names n on hier.id = n.id
left outer join names p on hier.parent = p.id
where
 n.name like concat(p_name, '%');

drop temporary table if exists hier;
drop temporary table if exists tmp;

end #

delimiter ;

-- call this sproc from your php

call names_hier(1, 'a');
call names_hier(3, 'k');