【问题标题】:Recursive SQL for a menu system用于菜单系统的递归 SQL
【发布时间】:2010-10-04 11:28:02
【问题描述】:

我有一个具有以下结构的菜单系统表和一些数据。

ID、文本、ParentID、DestinationID 1、应用程序,(空),(空) 2、游戏,(空),(空) 3,办公室,1,(空) 4、文本编辑、1、(空) 5,媒体,(空),(空) 6、字、3、1 7、Excel、3、2 8、孤岛危机、2、3

我需要的是一个查询,我可以将菜单 ID 传递给该查询,它将返回具有该 ID 作为子项的项目列表。但是,我需要它只返回具有到达目的地的有效路径的孩子。所以在上面的例子中,用户最初会看到(应用程序,游戏),当他选择应用程序时,他会看到(办公室)。文本编辑和媒体应该被省略,因为它们下面没有有效的目的地。

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

编辑

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

【问题讨论】:

标签: sql recursion


【解决方案1】:

在甲骨文中:

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 上的索引。首先,使用0 的假parentID 而不是NULL

【讨论】:

  • 不应该是 WHERE COALESCE(mp.ParentID, 0) = COALESCE(@currentParent, 0) 吗?当我使用 mc 时,我得到“无法绑定多部分标识符“mc.ParentID”。”将其更改为 mp 似乎有效,但我不明白为什么!根本没有对 dest 字段的引用。
  • 为什么什么?你的意思是为什么它有效?它选择您提供其父项且其 ID 在其他项父项中的所有项目,这似乎正是您想要的
  • 好的,我弄清楚它是如何工作的,但有一个缺陷。如果您有两到三层深度的菜单路径,则只有最后一层没有显示。我希望隐藏所有虚拟路径。
【解决方案2】:

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

【讨论】:

  • 公用表表达式是 ANSI 标准。如果可以的话,我会走这条路。
【解决方案3】:

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

【讨论】:

  • 我现在正在看这本书。我会在这件事上回复你:)
【解决方案4】:

SQL 不太擅长遍历任意深度层次结构。

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

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

【讨论】:

  • 在 SQLServer2008 中这是不正确的。公用表表达式也可以进行递归并且非常有效...
  • 即使 SQL 查询没有显示任意数量的连接,查询计划也需要它。你只是无法击败平手。
【解决方案5】:

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

这会给

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 > office > 应用程序和...

excel > 办公 > 应用程序

...大概应该在同一个菜单项上(父 ID 3)

我不确定您是如何选择菜单的,但我将按照以下原则工作:初始菜单按钮设置为 (null) 作为其参数,并且每次后续单击都会动态地按顺序保存下一个参数(其中似乎与您的 cmets 相匹配)

例如

点击顶级菜单:- 值为 (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)

【讨论】:

    【解决方案6】:

    正如其他人所指出的,标准 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,前 2 个字符代表每个产品的 ID,接下来的每个字符对标识树中的一个节点。所以我们看到产品 3 依赖于其他 2 个产品。产品 4 依赖于另外 2 个,同样,其中一个依赖于它的部分依赖于另外两个。

    此模型有很多冗余,但可以轻松计算特定产品需要多少螺丝,快速确定哪些零件需要木板或获取产品最终依赖的所有组件的列表(包括间接依赖项)等。而扫描某一层以下的树是一个简单的 LIKE 查询!

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

    此外,此表模型在 Access 和 mySQL 上也能很好地工作。无论如何,你不能拥有的是循环依赖。

    【讨论】:

      【解决方案7】:

      如果您在数据库中搜索的层次结构/树不经常更改,我建议使用改进的前序树遍历 (MPTT) 算法。这将需要不同的表架构,但允许您使用简单的 SQL 语句(无递归等)请求整个子树。

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

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

      现在,如果你想选择 Office 子树,你可以这样做:

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

      如果你的数据库不支持 BETWEEN 语句,你当然可以写 left > 10 AND left

      您的表格将如下所示:

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

      【讨论】:

        【解决方案8】:

        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-08-24
          • 2014-09-22
          • 1970-01-01
          • 2023-04-05
          • 1970-01-01
          • 1970-01-01
          • 2011-01-21
          • 2023-01-27
          相关资源
          最近更新 更多