1

我正在尝试在我的 PHP CMS 中构建一个动态菜单;页面/类别使用嵌套集模型进行组织。

全树:

根
 一个
 乙
  B1
   B1.1
   B1.2
  B2
   B2.1
   B2.1
 C
  C1
  C2
  C3
 D

我想将此结果集转换为一个无序列表,它只显示树的一部分。例如:如果我单击 B,我只想显示列表的以下部分:

一个
乙
 B1
 B2
C
D

接下来,如果我单击 B1,我希望显示此列表:

一个
乙
 B1
  B1.1
  B1.2
 B2
C
D

等等

我使用以下 SQL 查询从 (mysql) 数据库中获取所有节点:

选择 node.id、node.lft、node.rgt、node.name、
GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/" ) 作为路径,
(COUNT(parent.lft) - 1) 作为深度
FROM 页面作为节点,页面作为父级
在 parent.lft 和 parent.rgt 之间的 node.lft
AND (parent.hidden = "no" AND node.hidden = "no") AND parent.lft > 1
GROUP BY node.id ORDER BY node.lft

我设法在没有递归的情况下创建了完整列表(使用深度列),但我无法像上面显示的那样过滤菜单;我想我需要为每个节点获取父节点的 lft 和 rgt 值,并使用 PHP 过滤掉元素。但是如何在同一个查询中获取这些值?

关于如何实现这一目标还有其他建议吗?

提前致谢!

4

4 回答 4

2

以下查询将允许您利用 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 子句将过滤器应用于 group by 查询的结果。“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/ 。

如果您有任何问题,请告诉我。

高温下,

-蘸

于 2009-06-25T04:35:01.333 回答
0

我意识到这可能是一个老问题,但当我偶然发现同样的问题时,我决定提供一些意见,以便其他人也能从中受益。

-Dipins 的答案是我的进步所依据的答案,现在我认为我有一个没有所有“或”的解决方案。

只需将有部分替换为:

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 与通配符 (%) 连接起来,这意味着任何东西都可以在它之后出现。子字符串删除节点自己的名称和破折号,使 path_of_node 实际上是它的节点的路径。因此,如果我们单击链接以打开新的子树,A/B/B1 将变为“A/B%”,这与我们的请求相匹配。

我有depth = 1的原因是我可能在同一棵树中有多个菜单,我不希望人们看到“菜单-富人”、“菜单-穷人”之类的东西名字是反正。我的集合的顶级节点是一种持有节点,我将它们排除在实际结果之外。

我希望这被证明对某人有用,至少我花了几个小时寻找解决方案,然后想出了它。

我想,几天后你可以通过查看www.race.fi来确认这是否有效

编辑/注意:

我又测试了一些,似乎订购有问题。这是我的查询的快速复制粘贴,并具有正确的顺序。有一些不必要的东西,比如语言环境、内容和 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";
于 2009-09-23T21:26:36.363 回答
0

一个朋友写的关于如何从头开始构建嵌套集合的好帖子在这里;MySQL中的嵌套集

也许它对你有帮助。

于 2009-09-23T23:17:38.423 回答
-1

隐藏不需要的元素是否适合您的项目范围?例如(CSS):

  • .menu li > ul {显示:无;}
  • .menu li.clicked > ul {display: block;}

然后使用 javascript 将“clicked”类添加到任何被点击的 <li> 元素。请注意,此 CSS 在 IE6 中不起作用。

于 2009-06-21T02:23:49.310 回答