菜单系统的递归SQL

时间:2009-01-30 13:51:16

标签: sql recursion

我有一个菜单系统表,其中包含以下结构和一些数据。

ID, Text, ParentID, DestinationID
1, Applications, (null), (null)
2, Games, (null), (null)
3, Office, 1, (null)
4, Text Editing, 1, (null)
5, Media, (null), (null)
6, Word, 3, 1
7, Excel, 3, 2
8, Crysis, 2, 3

我需要的是一个可以传递菜单ID的查询,它将返回一个具有该ID作为子项的项目列表。但是,我需要它才能返回具有到目的地的有效路径的孩子。因此,在上面的示例中,用户将首先显示(应用程序,游戏),当他选择应用程序时,他将被呈现(Office)。应省略文本编辑和媒体,因为它们下面没有有效的目的地。

最棘手的是,任何给定的菜单都没有预定的深度。

修改

今天,问题出现在MS SQL 2008上,但在过去两周内,我需要类似的SQLite和SQL CE解决方案。理想的解决方案不应该绑定到任何特定的SQL引擎。

8 个答案:

答案 0 :(得分:7)

仅限SQL服务器,但它听起来像是Common Table Expressions的作业。

答案 1 :(得分:3)

在Oracle中:

SELECT m.*, level
FROM my_table m
START WITH
  Id = :startID
CONNECT BY
  ParentID = PRIOR Id
  AND DestinationID IS NOT NULL

使用单个查询无法在ANSI SQL中执行此操作。您可以为表创建一个额外的列AccessPath

ID, Text, ParentID, DestinationID AccessPath
1, Applications, (null), (null), "1"
2, Games, (null), (null), "2"
3, Office, 1, (null), "1.3"
4, Text Editing, 1, (null), "1.4"
5, Media, (null), (null), "5"
6, Word, 3, 1, "1.3.6"
7, Excel, 3, 2, "1.3.7"
8, Crysis, 2, 3, "1.2.8"

,并查询:

SELECT mp.Id, mp.Text
FROM my_table mp, my_table mc
WHERE mp.parentID = @startingParent
 AND mc.Id <> mp.Id
 AND SUBSTR(mc.AccessPath, LENGTH(mp.AccessPath)) = mp.AccessPath
GROUP BY
 mp.Id, mp.Text

NULL开头是一个坏主意,因为ParentID上的索引在这种情况下无法使用。首先,使用假parentID 0代替NULL

答案 2 :(得分:3)

如果您在数据库中进行处理的层次结构/树不经常更改,我建议使用修改的预订树遍历(MPTT)算法。这将需要一个不同的表模式,但允许您使用简单的SQL语句请求整个子树(没有递归等)。

Storing Hierarchical Data in a Database上的文章详细介绍了这种方法。

在您的示例中,您将获得以下树,我将红色数字称为 left 值,并将绿色右侧值称为节点。

alt text

现在,如果要选择 Office 子树,可以执行以下操作:

SELECT * FROM tree WHERE left BETWEEN 10 AND 15 AND destination IS NOT NULL

如果您的数据库不支持BETWEEN语句,您当然可以写左&gt; 10和左&lt; 15而不是。

你的表格如下:

name         | left | right | destination
------------------------------------------ 
root         | 1    | 17    | NULL
Applications | 7    |  16   |  ...
...

答案 3 :(得分:1)

如果这是您感兴趣的问题(或困扰您),您可能需要查看:Joe Celko's Trees and Hierarchies in SQL for Smarties

答案 4 :(得分:1)

正如其他人所指出的那样,标准ANSI SQL无法做你想做的事情。对于这样的事情,我曾经在SQL 2000上实现了一个系统,用于跟踪前雇主制造的产品组件 - 每个“产品”可能是原子组件,比如说,螺旋A500。该组件可用于“复合”组件:一些A500螺丝加上6个B120木板符合C90“时尚工具箱”。那个盒子,加上更多的螺丝和一个马达“M500”可以符合木工工具。

我设计了一个像这样的表“产品”:

ID, PartName, Description
1, A500, "Screw A500"
2, B120, "Wood panel B120"
3, C90, "Stylish tool box C90"
4, M500, "Wood cutter M500"

“ProductComponent”表格如下:

Hierarchy, ComponentID, Amount
0301, 1, 24
0302, 2, 6
0401, 1, 3
0402, 3, 1
0403, 4, 1
040201, 1, 24
040202, 2, 6

技巧是:字段层次结构是一个VARCHAR,前两个字符代表每个产品的ID,每个下一对字符标识树中的一个节点。所以我们看到产品3依赖于其他2种产品。产品4依赖于另外两个产品,其中一个产品依赖于另外两个产品。

此模型中有许多冗余,但可以轻松计算特定产品所需的螺钉数量,快速确定哪些部件需要木质面板或获取产品最终所依赖的所有组件的列表(包括间接依赖性)扫描一定水平以下的树是一个简单的LIKE查询!

通过以十六进制表示形式使用2个字符,我将产品限制为直接依赖于最多256个其他产品(反过来又取决于其他东西)。您可以将其更改为使用base 36(26个字母加10个数字)或base-64(如果您需要更多)。

此外,这个表模型在Access和mySQL上也能很好地工作。你不能拥有的是任何方式的循环依赖。

答案 5 :(得分:0)

SQL在处理任意深度层次结构方面不是很好。

如果这些记录少于1000条,我会把它们全部拿到应用程序中并在那里构建图形。

如果这些记录超过1000条,我会将它们分组为大约1000的原始子树(通过添加SubtreeID外键)并获取每个子树...然后在应用程序中构建子树的图形。 / p>

答案 6 :(得分:0)

我要做的第一件事是删除目标列 - 它在层次结构方面毫无意义(它实际上似乎是一种第二主键,用于表示活动子行的方式)< / p>

这会给出

ID, Item, parentID
1, Applications, (null)
2, Games, (null)
3, Office, 1
4, Text Editing, 1
5, Media, (null)
6, Word, 3
7, Excel, 3
8, Crysis, 2

例如......

word&gt;办公室&gt;申请和......

excel&gt;办公室&gt;应用

...应该可能在同一个菜单项(父ID 3)

我不确定你是如何选择菜单的,但是我会按照原则设置一个初始菜单按钮设置为(null)作为参数,每次后续点击动态顺序保存下一个参数(其中似乎符合您的意见)

e.g。

单击顶级菜单: - 值为(null)

点击应用程序: - 值为1

单击Office: - 值为3

假设destinationID除了显示活动的子链接(允许您删除它)之外什么都不做,那么代码将如下:

with items (nodeID, PID, list) as
  (select id, ParentID, item
    from menu
    where id = 9
    union all
  select id, ParentID, item
    from menu
    inner join items on nodeID = menu.ParentID
  )
select *
from items 
where (pid = 9)
and nodeID in (select parentid from menu) 

这适用于MSSQL 2005 +

如果出于某种其他原因需要目标ID,则可以按如下方式修改代码(例如,如果需要返回节点id尚未设置为父ID的最低级别):

with items (nodeID, PID, list, dest) as
  (select id, ParentID, item, destinationID
    from menu
    where id = 9
    union all
  select id, ParentID, item, destinationID
    from menu
    inner join items on nodeID = menu.ParentID
)
select *
from items 
where (pid = 9)
and (nodeID in (select parentid from menu) 
  or dest is not null)

答案 7 :(得分:0)

https://geeks.ms/jirigoyen/2009/05/22/recursividad-con-sql-server/

ALTER PROCEDURE [dbo].[Usuarios_seguridad_seleccionar]
AS
BEGIN    

    DECLARE @minClave int
    SELECT @minClave = MIN(Clave) FROM dbo.Usuarios_seguridad;

    WITH UsuariosAccesos AS(

        SELECT top 1 us1.Padre,us1.Clave,us1.Variable,us1.Modulo,us1.Contenido,us1.Acceso,us1.Imagen 
        FROM dbo.Usuarios_seguridad us1 
        WHERE us1.Clave = @minClave
        UNION ALL
        SELECT top 100 percent us2.Padre,us2.Clave,us2.Variable,us2.Modulo,us2.Contenido,us2.Acceso,us2.Imagen 
        FROM dbo.Usuarios_seguridad us2 
        INNER JOIN UsuariosAccesos AS us3 ON us3.Clave = us2.Padre  
        WHERE us2.Clave <> @minClave 
    )

    SELECT TOP 100 PERCENT ia.Padre,ia.Clave,ia.Variable,ia.Modulo,ia.Contenido,ia.Acceso,ia.Imagen 
    FROM UsuariosAccesos ia
    ORDER BY padre, clave
END
GO