2009-06-18 5 views
0

PHP CMS에서 동적 메뉴를 작성하려고합니다. 페이지/카테고리는 중첩 세트 모델을 사용하여 구성됩니다.중첩 세트를 사용하여 동적 메뉴 구성

전체 트리 :

 
root 
A 
B 
    B1 
    B1.1 
    B1.2 
    B2 
    B2.1 
    B2.1 
C 
    C1 
    C2 
    C3 
D 

나는 단지 나무의 일부를 표시하는 unordererd 목록으로 설정이 결과를 변환 할. 예를 들어 :

 
A 
B 
B1 
    B1.1 
    B1.2 
B2 
C 
D 
: 나는 B1 클릭하면

 
A 
B 
B1 
B2 
C 
D 

다음, 나는이 목록을 보여주고 싶은 : 나는 B를 클릭하면 , 나는 목록의 다음 부분을 보여주고 싶은

나는 (MySQL의) 데이터베이스에서 모든 노드를 얻기 위해 다음과 같은 SQL 쿼리를 사용

SELECT node.id, node.lft, node.rgt, node.name, 
GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path, 
(COUNT(parent.lft) - 1) AS depth 
FROM pages AS node, pages AS parent 
WHERE node.lft BETWEEN parent.lft AND parent.rgt 
AND (parent.hidden = "no" AND node.hidden = "no") AND parent.lft > 1 
GROUP BY node.id ORDER BY node.lft

(깊이 열을 사용하여) 재귀없이 전체 목록을 만들 수 있었지만 위의 그림과 같이 메뉴를 필터링 할 수는 없습니다. 각 노드에 대해 부모의 lft 및 rgt 값을 가져와 PHP를 사용하여 요소를 필터링해야한다고 생각합니다. 그러나 같은 쿼리에서이 값을 얻으려면 어떻게해야합니까?

이 작업을 수행하는 방법에 대한 다른 제안이 있습니까?

미리 감사드립니다.

답변

-1

원하지 않는 요소를 숨기려면 프로젝트 범위에 맞습니까? 예 (CSS)

  • .menu 리> UL {디스플레이 : 없음}
  • .menu li.clicked> UL {디스플레이 : 블록}

그런 다음 추가 자바 스크립트를 사용하여 클래스는 클릭 된 모든 < li> 요소에 "클릭"되었습니다. 이 CSS는 IE6에서는 작동하지 않습니다.

+0

저는 이미 이와 비슷한 것을 시도해 보았습니다. 하지만 메뉴에 많은 요소가 포함되어있는 경우 사용자는 매우 적은 수의 요소 만보고있는 순서가없는 큰 목록을로드합니다. 그게 내가 피하려고하는거야. 메뉴의 기능이 자바 스크립트에 의존하지 않기를 바랍니다. –

1

다음 쿼리는 SQL의 having 절 및 MySQL의 group_concat 기능을 활용하여 경로 또는 경로 집합을 열 수 있도록합니다.

는 다음 쿼리는 (머리 제외) 당신에게 전체 트리를 제공
drop table nested_set; 

CREATE TABLE nested_set (
id INT, 
name VARCHAR(20) NOT NULL, 
lft INT NOT NULL, 
rgt INT NOT NULL 
); 

INSERT INTO nested_set (id, name, lft, rgt) VALUES (1,'HEAD',1,28); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (2,'A',2,3); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (3,'B',4,17); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (4,'B1',5,10); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (5,'B1.1',6,7); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (6,'B1.2',8,9); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (7,'B2',11,16); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (8,'B2.1',12,13); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (9,'B2.2',14,15); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (10,'C',18,25); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (11,'C1',19,20); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (12,'C2',21,22); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (13,'C3',23,24); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (14,'D',26,27); 

:

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 

의 출력으로 다음

내가 사용하는 테이블 정의 및 샘플 데이터입니다 샘플 데이터와 비교해 보면 다음과 같습니다.

+------+-----+-----+------+-----------+-------+ 
| id | lft | rgt | name | path  | depth | 
+------+-----+-----+------+-----------+-------+ 
| 2 | 2 | 3 | A | A   |  0 | 
| 3 | 4 | 17 | B | B   |  0 | 
| 4 | 5 | 10 | B1 | B/B1  |  1 | 
| 5 | 6 | 7 | B1.1 | B/B1/B1.1 |  2 | 
| 6 | 8 | 9 | B1.2 | B/B1/B1.2 |  2 | 
| 7 | 11 | 16 | B2 | B/B2  |  1 | 
| 8 | 12 | 13 | B2.1 | B/B2/B2.1 |  2 | 
| 9 | 14 | 15 | B2.2 | B/B2/B2.2 |  2 | 
| 10 | 18 | 25 | C | C   |  0 | 
| 11 | 19 | 20 | C1 | C/C1  |  1 | 
| 12 | 21 | 22 | C2 | C/C2  |  1 | 
| 13 | 23 | 24 | C3 | C/C3  |  1 | 
| 14 | 26 | 27 | D | D   |  0 | 
+------+-----+-----+------+-----------+-------+ 

위의 쿼리는 다양한 섹션을 여는 데 필요한 컨트롤을 제공합니다 :

having 
depth = 0 
or ('<PATH_TO_OPEN>' = left(path, length('<PATH_TO_OPEN>')) 
    and depth = length('<PATH_TO_OPEN>') - length(replace('<PATH_TO_OPEN>', '/', '')) + 1) 

having 절은 쿼리에 의해 그룹 결과에 필터를 적용합니다."depth = 0"부분은 항상 기본 메뉴 노드 (A, B, C 및 D)가 있는지 확인하는 것입니다. 다음 부분은 열려있는 노드를 제어하는 ​​부분입니다. 노드의 경로를 열려고하는 설정된 경로 ('')와 비교하여 일치하는지 확인하고 경로의 레벨 만 열도록합니다. 로직의 전체 또는 섹션을 필요에 따라 복제하고 필요할 때 추가하여 여러 경로를 열 수 있습니다. ''가 후행 슬래시 (/)로 끝나지 않도록하십시오. 그것에 관하여

=========Open B========== 

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 
having 
depth = 0 
or ('B' = left(path, length('B')) 
    and depth = length('B') - length(replace('B', '/', '')) + 1) 

+------+-----+-----+------+------+-------+ 
| id | lft | rgt | name | path | depth | 
+------+-----+-----+------+------+-------+ 
| 2 | 2 | 3 | A | A |  0 | 
| 3 | 4 | 17 | B | B |  0 | 
| 4 | 5 | 10 | B1 | B/B1 |  1 | 
| 7 | 11 | 16 | B2 | B/B2 |  1 | 
| 10 | 18 | 25 | C | C |  0 | 
| 14 | 26 | 27 | D | D |  0 | 
+------+-----+-----+------+------+-------+ 

=========Open B and B/B1========== 

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 
having 
depth = 0 
or ('B' = left(path, length('B')) 
    and depth = length('B') - length(replace('B', '/', '')) + 1) 
or ('B/B1' = left(path, length('B/B1')) 
    and depth = length('B/B1') - length(replace('B/B1', '/', '')) + 1) 

+------+-----+-----+------+-----------+-------+ 
| id | lft | rgt | name | path  | depth | 
+------+-----+-----+------+-----------+-------+ 
| 2 | 2 | 3 | A | A   |  0 | 
| 3 | 4 | 17 | B | B   |  0 | 
| 4 | 5 | 10 | B1 | B/B1  |  1 | 
| 5 | 6 | 7 | B1.1 | B/B1/B1.1 |  2 | 
| 6 | 8 | 9 | B1.2 | B/B1/B1.2 |  2 | 
| 7 | 11 | 16 | B2 | B/B2  |  1 | 
| 10 | 18 | 25 | C | C   |  0 | 
| 14 | 26 | 27 | D | D   |  0 | 
+------+-----+-----+------+-----------+-------+ 

=========Open B and B/B1 and C========== 

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 
having 
depth = 0 
or ('B' = left(path, length('B')) 
    and depth = length('B') - length(replace('B', '/', '')) + 1) 
or ('B/B1' = left(path, length('B/B1')) 
    and depth = length('B/B1') - length(replace('B/B1', '/', '')) + 1) 
or ('C' = left(path, length('C')) 
    and depth = length('C') - length(replace('C', '/', '')) + 1) 

+------+-----+-----+------+-----------+-------+ 
| id | lft | rgt | name | path  | depth | 
+------+-----+-----+------+-----------+-------+ 
| 2 | 2 | 3 | A | A   |  0 | 
| 3 | 4 | 17 | B | B   |  0 | 
| 4 | 5 | 10 | B1 | B/B1  |  1 | 
| 5 | 6 | 7 | B1.1 | B/B1/B1.1 |  2 | 
| 6 | 8 | 9 | B1.2 | B/B1/B1.2 |  2 | 
| 7 | 11 | 16 | B2 | B/B2  |  1 | 
| 10 | 18 | 25 | C | C   |  0 | 
| 11 | 19 | 20 | C1 | C/C1  |  1 | 
| 12 | 21 | 22 | C2 | C/C2  |  1 | 
| 13 | 23 | 24 | C3 | C/C3  |  1 | 
| 14 | 26 | 27 | D | D   |  0 | 
+------+-----+-----+------+-----------+-------+ 

다음

당신은 당신이 원하는 출력을 얻기 위해 쿼리를 구성 할 방법을 보여 몇 가지 출력 예입니다. 열어야하는 각 경로마다 해당 섹션이나 섹션을 복제하면됩니다.

MySQL의 중첩 세트 작업에 대한 일반적인 정보가 필요한 경우를 대비하여 http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/을 참조하십시오.

궁금한 점이 있으면 알려주세요.

HTH,

-Dipin

0

나는이 오래된 질문 수 있습니다 실현,하지만 난 같은 문제를 우연히 발견했다, 나는 다른 사람들이 너무 혜택을 누릴 수 있도록 몇 가지 입력을 제공하기로 결정했다.

-Dipins 대답은 제가 진행 한 것입니다. 지금은 모든 OR이없는 해결책이 있다고 생각합니다.

그냥와 가진 부품을 교체 : 그것은 무엇이며,

HAVING 
    depth = 1 
    OR 
    '".$path."' LIKE CONCAT(SUBSTRING(path, 1, (LENGTH(path) - LENGTH(menu_node_name) -1)), '%') 

$path = requested path. parent node's path that the user clicked, "A/B" for example 

path = the path of the current node including the nodes name "A/B/B1" for example, which is a child for the node the user clicked. 

menu-node-name = the name of the node in progress, "B1" for example. 

는 요청 된 경로를 비교, 노드의 경로와 A/B/B1 말할 수 있습니다. 노드의 경로가 힘든 작업이 필요했습니다. LIKE path-of-node %는 작동했지만 상위 레벨 만 제공하고 다른 노드는 같은 레벨에 제공하지 않았습니다. 이 버전은 않습니다.

WE는 path_of_node와 와일드 카드 (%)를 연결합니다. 하위 문자열 을 제거합니다. 노드 이름과 대시는을 제거하고 실제로 path_of_node 경로는 부모 노드의 노드입니다. 따라서 A/B/B1은 "A/B %"가되어 새로운 하위 트리를 열 수있는 링크를 클릭하면 우리의 요청과 일치합니다.

깊이가 1 인 이유는 동일한 트리에서 여러 개의 메뉴가있을 수 있기 때문에 "MENU-FOR-RICH-PEOPLE", "MENU-FOR-POOR-PEOPLE "또는 어쨌든 이름이 무엇이든간에. 내 세트의 최상위 레벨 노드는 종류가있는 노드이므로 실제 결과에서 제외됩니다.

나는 이것이 누군가를 위해 유용하다고 증명되기를 바란다. 최소한 나는 몇 시간 동안 해결책을 찾은 다음 그걸 생각해 냈다.

나는 당신이 www.race.fi

EDIT/주보고했다 확인할 수 있습니다 몇 일, 생각 :

내가 좀 더 테스트가 순서가 잘못된 것을 보인다. 다음은 올바른 질의를 사용하여 쿼리를 신속하게 복사하는 방법입니다. 로케일, 컨텐트 및 content_localised와 같은 불필요한 요소가 있지만 중요한 점은 분명해야합니다.

SELECT 
    REPEAT('-',(COUNT(MENU.par_name) - 2)) as indt, 
    GROUP_CONCAT(MENU.par_name ORDER BY MENU.par_lft SEPARATOR '/') AS path, 
    (COUNT(MENU.par_lft) - 1) AS depth, 
    MENU.*, 
    MENU.content 
FROM 
    (SELECT 
     parent.menu_node_name AS par_name, 
     parent.lft AS par_lft, 
     node.menu_node_id, 
     node.menu_node_name, 
     node.content_id, 
     node.node_types, 
     node.node_iprop, 
     node.node_aprop, 
     node.node_brands, 
     node.rgt, 
     node.lft, 
     [TPF]content_localised.content 

    FROM [TPF]" . $this->nestedset_table . " AS node 
    JOIN [TPF]" . $this->nestedset_table . " AS parent 
      ON node.lft BETWEEN parent.lft AND parent.rgt 
    JOIN [TPF]content 
     ON node.content_id = [TPF]content.content_id 
    JOIN [TPF]content_localised 
     ON [TPF]content.content_id = [TPF]content_localised.content_id 
    JOIN [TPF]locales 
     ON [TPF]content_localised.locale_id = [TPF]locales.locale_id 

    ORDER BY node.rgt, FIELD(locale, '" . implode("' , '", $locales) . "', locale) ASC 
    ) AS MENU 

GROUP BY MENU.menu_node_id 
HAVING depth = 1 
    OR '".$path."' LIKE CONCAT(SUBSTRING(path, 1, (LENGTH(path) - LENGTH(MENU.menu_node_name) -1)), '%') 
    AND depth > 0 
ORDER BY MENU.lft"; 
0

친구가 작성한 중첩 세트를 작성하는 방법에 대한 좋은 게시물은 여기에 있습니다. Nested Set in MySQL

아마도 도움이 될 것입니다.