2011-03-07 4 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 

예 : 나는 (즉 parentid=0와) 정상 부모에 도달 할 때까지 내 현재 메뉴, 나는 부모 Id 2과 그들의 부모와 부모의 부모를 가진 모든 필드를 선택합니다 있습니다.

단일 쿼리를 사용하여 수집 할 수 있습니까? 그렇다면 달성 방법은 무엇입니까?

답변

2

데이터 구조를 제어 할 수있는 경우이 데이터를 저장하는 더 좋은 방법이 있습니다. 그러면 원하는 데이터를 처리 할 수 ​​있으며 사용자가 원하는대로 처리 할 수 ​​있습니다.

당신이하고있는 일은 일반적으로 인접 목록 모델이라고합니다. 계층 적 데이터를 저장하고 검색하는 훨씬 효율적인 방법 인 중첩 세트 모델을 확인해야합니다.

여기 좋은 튜토리얼 good tutorial here

그가 몇 년 동안 이것에 대해 기록 된대로 조 셀코 올바른 방향으로 당신에게 링크를 많이 줄 것이다 웹 사이트에서의 빠른 검색이 있습니다.

희망이 아닌 재귀 저장 프로 시저과 인접리스트 구현을 사용

+1

"훨씬 더 효율적인 방법"--- 그것은 매우 논의할만한 문구입니다. NS는 종종 트리 수정에서 ** 성능이 떨어집니다 **. – zerkms

+0

zerkms는 정확하지만 자주 수정할 필요가없는 경우 대답이 될 수 있습니다. –

+0

흥미로운 기사, 감사합니다 – Simon

4

매우 간단한 단일 통화 솔루션을하는 데 도움이됩니다. 전염병과 같은 중첩 된 세트를 피하는 것이 좋습니다 - 교실에있는 것이 가장 좋습니다!

당신이해야 할 일은 당신의 PHP에서 저장된 procs 중 하나를 호출하는 것입니다!

call menus_hier_downward(1); 
call menus_hier_upward(3); 

단일체 - 희망이 도움이 :)

예 내가 당신에게 두 가지 예를 들어 저장 프로 시저를 제공 한

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

"위쪽"과 "아래쪽"을 정의하십시오 - 모두 당신의 관점에 달려 있습니다.나는 명확하지 않기 때문에 "rootwards"와 "leafwards"를 선호합니다. – geoidesic

관련 문제