我正在尝试在PHP CMS中构建动态菜单;页面/类别使用嵌套集模型进行组织。
完整的树:
root A B B1 B1.1 B1.2 B2 B2.1 B2.1 C C1 C2 C3 D
我想将此结果集转换为unordererd列表,该列表仅显示树的一部分。 例如: 如果我点击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
我设法创建了没有递归的完整列表(使用深度列),但我不能过滤菜单,如上所示; 我想我需要为每个节点获取父级的lft和rgt值,并使用PHP过滤掉元素。 但是如何在同一个查询中获取这些值呢?
关于如何实现这个目的还有其他建议吗?
提前致谢!
答案 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);
以下查询为您提供整个树(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)
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
答案 1 :(得分: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与通配符(%)连接起来,这意味着任何东西都可以在它之后。子串 REMOVES 节点拥有的名称和短划线,使path_of_node实际上是它的父级节点的路径。因此,如果我们点击链接打开一个新的子树,A / B / B1就会变成“A / B%”,这与我们的请求相符。
我有深度= 1的原因是我可能在同一个树中有多个菜单,我不希望人们看到类似“菜单丰富的人”,“菜单为穷人”的东西或者无论名称是什么。我的集合的顶级节点有点保持节点,我将它们从实际结果中排除。
我希望这证明对某人有用,至少我找了几个小时的解决方案然后想出来。
我想,有几天你可以通过查看www.race.fi
来确认这是有效的编辑/注意:
我测试了一些,看起来排序有问题。这是我的查询的快速copypaste与正确的顺序。有一些不必要的东西,如语言环境,内容和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";
答案 2 :(得分:0)
关于如何从朋友写的基础上构建嵌套集的好帖子就在这里; Nested Set in MySQL
也许这对你有帮助。
答案 3 :(得分:-1)
只是隐藏不需要的元素,是否适合您的项目范围?例如(css):
然后使用javascript将“点击”类添加到任何&lt; li&gt;被点击的元素。请注意,此CSS在IE6中不起作用。