我有一个数据库,其名称树可以总共下降到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”。
我想不出任何简单的方法,所以任何帮助都表示赞赏。
答案 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');