2011-03-07 48 views
3

我有一個數據庫表菜單,其中包含id, name and parentid使用類似遞歸函數的查詢選擇祖先

我在數據庫中有以下值,我想收集所有字段,包括使用查詢的父菜單。

id name  parentid 
    1 File   0 
    2 New   1 
    3 Document  2 
    4 Image  2 
    5 Edit   0 
    6 Copy   5 
    7 Paste  5 

例子:我有2個是我目前的菜單,我要選擇具有父Id 2和他們的父母以及他們父母的父母的所有字段,直到我到達山頂父(與parentid=0即)。

是否可以使用單個查詢來收集它?如果是的話,它是如何實現的?

回答

2

如果你可以控制你的數據結構,那麼存儲這些數據的更好的方法就是讓你做你需要的東西,而且這要比試着繼續下去更容易。

你在做什麼通常稱爲鄰接表模型。您應該查看嵌套集合模型,這是存儲和檢索分層數據的更有效的方式。

這裏有一個很好的教程good tutorial here

和網絡的喬·塞科會給你很多鏈接在正確的方向,因爲他一直在寫關於這個多年上快速搜索。

希望這有助於使用與非遞歸存儲過程鄰接表實現

+1

「更有效的方式」 - 這是一個非常討論性的短語。在通常的樹形修改中,NS性能較差**。 – zerkms 2011-03-07 04:22:58

+0

zerkms是正確的,但如果它不需要經常修改,那麼它可能是答案。 – 2011-03-07 04:28:01

+0

有趣的文章,謝謝 – Simon 2011-03-07 04:45:59

4

相當簡單單的呼叫解決方案。建議避免像鼠疫這樣的嵌套集 - 最好留在教室裏!

所有你需要做的就是從你的php中調用這些存儲過程之一!

​​3210

Simples - 希望它幫助:)

示例結果

call menus_hier_downward(1); 
+---------+-----------+-----------+------------------+-------+ 
| menu_id | menu_name | parent_id | parent_menu_name | depth | 
+---------+-----------+-----------+------------------+-------+ 
|  1 | File  |  NULL | NULL    |  0 | 
|  2 | New  |   1 | File    |  1 | 
|  3 | Document |   2 | New    |  2 | 
|  4 | Image  |   2 | New    |  2 | 
+---------+-----------+-----------+------------------+-------+ 
4 rows in set (0.00 sec) 

call menus_hier_upward(3); 
+---------+-----------+-----------+------------------+-------+ 
| menu_id | menu_name | parent_id | parent_menu_name | depth | 
+---------+-----------+-----------+------------------+-------+ 
|  3 | Document |   2 | New    |  1 | 
|  2 | New  |   1 | File    |  2 | 
|  1 | File  |  NULL | NULL    |  3 | 
+---------+-----------+-----------+------------------+-------+ 
3 rows in set (0.00 sec) 

我提供你兩個示例存儲過程。一個向下工作。全腳本如下:

表實施例

drop table if exists menus; 
create table menus 
(
menu_id smallint unsigned not null auto_increment primary key, 
name varchar(255) not null, 
parent_id smallint unsigned null, 
key (parent_id) 
) 
engine = innodb; 

insert into menus (name, parent_id) values 
('File',null), 
('New',1), 
    ('Document',2), 
    ('Image',2), 
('Edit',null), 
('Copy',5), 
('Paste',5); 

向下存儲過程

drop procedure if exists menus_hier_downward; 

delimiter # 

create procedure menus_hier_downward 
(
in p_menu_id smallint unsigned 
) 
begin 

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

create temporary table hier(
parent_id smallint unsigned, 
menu_id smallint unsigned, 
depth smallint unsigned 
)engine = memory; 

insert into hier select parent_id, menu_id, v_dpth from menus where menu_id = p_menu_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 menus m inner join hier on m.parent_id = hier.menu_id and hier.depth = v_dpth) then 

     insert into hier select m.parent_id, m.menu_id, v_dpth + 1 
      from menus m inner join tmp on m.parent_id = tmp.menu_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 
m.menu_id, 
m.name as menu_name, 
p.menu_id as parent_id, 
p.name as parent_menu_name, 
hier.depth 
from 
hier 
inner join menus m on hier.menu_id = m.menu_id 
left outer join menus p on hier.parent_id = p.menu_id; 

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

end # 

delimiter ; 

向上存儲過程

drop procedure if exists menus_hier_upward; 

delimiter # 

create procedure menus_hier_upward 
(
in p_menu_id smallint unsigned 
) 
begin 

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

create temporary table hier(
parent_id smallint unsigned, 
menu_id smallint unsigned, 
depth smallint unsigned 
)engine = memory; 

insert into hier select menu_id, null, v_dpth from menus where menu_id = p_menu_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 menus m inner join hier on m.menu_id = hier.parent_id and hier.depth = v_dpth) then 

     insert into hier select m.parent_id, m.menu_id, v_dpth + 1 
      from menus m inner join tmp on m.menu_id = tmp.parent_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 
m.menu_id, 
m.name as menu_name, 
p.menu_id as parent_id, 
p.name as parent_menu_name, 
hier.depth 
from 
hier 
inner join menus m on hier.menu_id = m.menu_id 
left outer join menus p on hier.parent_id = p.menu_id; 

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

end # 

delimiter ; 
+0

請定義「向上」和「向下」 - 這一切都取決於你的觀點。由於這是明確的,我更喜歡「rootward」和「leafwards」。 – geoidesic 2014-12-31 12:21:16