[英]Build dynamic menu using Nested Sets
我正在嘗試在PHP CMS中建立一個動態菜單; 頁面/類別使用嵌套集模型進行組織。
全樹:
root A B B1 B1.1 B1.2 B2 B2.1 B2.1 C C1 C2 C3 D
我想將此結果集轉換為一個無序列表,該列表僅顯示樹的一部分。 例如:如果單擊B,則只顯示列表的以下部分:
A B B1 B2 C D
接下來,如果我單擊B1,我希望顯示此列表:
A B B1 B1.1 B1.2 B2 C D
等等
我使用以下SQL查詢從(mysql)數據庫獲取所有節點:
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
我設法創建了沒有遞歸的完整列表(使用depth列),但是我不能像上面顯示的那樣過濾菜單。 我想我需要獲取每個節點的父級的lft和rgt值,並使用PHP過濾掉元素。 但是,如何在同一查詢中獲得這些值?
關於如何實現這一目標還有其他建議嗎?
提前致謝!
以下查詢將允許您利用SQL的Have子句和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);
以下查詢為您提供整棵樹(HEAD除外):
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)
hading子句將過濾器應用於按查詢分組的結果。 “ 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,
-蘸
我意識到這可能是一個古老的問題,但是當我偶然發現相同的問題時,我決定提供一些意見,以便其他人也能從中受益。
-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,這意味着后面可以包含任何內容。 子字符串會刪除節點自己的名稱和破折號,從而使path_of_node實際上是其父節點的路徑。 因此,如果我們單擊鏈接打開新的子樹,則A / B / B1變為與我們的請求匹配的“ A / B%”。
我的depth = 1的原因是我可能在同一棵樹中擁有多個菜單,並且我不希望人們看到“ MENU-FOR-RICH-PEOPLE”,“ MENU-FOR-POOR-PEOPLE”之類的內容無論如何,名字都是。 我的集合的頂層節點是一個持有節點,我將它們從實際結果中排除。
我希望這對某人有用,至少我尋找了幾個小時的解決方案,然后提出了解決方案。
我認為,幾天之內您可以通過訪問www.race.fi確認此方法有效
編輯/注意:
我測試了一些,看來訂購有誤。 這是正確排序的查詢的快速復制粘貼。 有一些不必要的內容,例如語言環境,內容和content_localized,但要點很明確。
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";
一位朋友寫的一篇有關如何從頭開始構建嵌套集的好文章。 MySQL中的嵌套集
也許對您有幫助。
僅隱藏不需要的元素是否適合您的項目范圍? 例如(css):
然后使用javascript將“已單擊”類添加到已單擊的任何<li>元素中。 請注意,此CSS在IE6中不起作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.