【问题标题】:SQL Recursive TablesSQL 递归表
【发布时间】:2010-09-08 17:48:02
【问题描述】:

我有以下表,groups 表包含分层排序的组,group_member 存储用户所属的组。

groups
---------
id  
parent_id
name

group_member
---------
id
group_id
user_id

ID  PARENT_ID  NAME
---------------------------
1   NULL       Cerebra
2   1          CATS 
3   2          CATS 2.0 
4   1          Cerepedia 
5   4          Cerepedia 2.0
6   1          CMS 

ID GROUP_ID USER_ID
---------------------------
1  1        3
2  1        4
3  1        5
4  2        7
5  2        6
6  4        6
7  5        12
8  4        9
9  1        10

我想检索给定用户的可见组。也就是说,用户所属的组和这些组的子组。比如上面的数据:

USER  VISIBLE_GROUPS
9     4, 5 
3     1,2,4,5,6
12    5 

我使用递归和几个数据库查询来获取这些值。但我想知道是否可以使用单个 SQL 查询来提高我的应用程序性能。我正在使用 MySQL。

【问题讨论】:

    标签: sql mysql


    【解决方案1】:

    我认为不使用递归就无法做到这一点。您可以使用 mySQL 使用单个存储过程来完成它,但默认情况下,存储过程中不允许递归。 This article 包含有关如何启用递归的信息。我不确定这与多查询方法相比对性能有多大影响。 mySQL 可能会对存储过程进行一些优化,否则我希望性能相似。

    【讨论】:

      【解决方案2】:

      不知道您是否有用户表,所以我通过存储在 Group_Member 表中的 User_ID 获取列表...

      SELECT GroupUsers.User_ID,
              (
              SELECT 
                  STUFF((SELECT ',' + 
                  Cast(Group_ID As Varchar(10))
                  FROM Group_Member Member (nolock) 
                  WHERE Member.User_ID=GroupUsers.User_ID
              FOR XML PATH('')),1,1,'') 
              ) As Groups
      FROM (SELECT User_ID FROM Group_Member GROUP BY User_ID) GroupUsers
      

      返回:

      User_ID    Groups
      3          1
      4          1
      5          1
      6          2,4
      7          2
      9          4
      10         1
      12         5
      

      根据您表中的数据,这似乎是正确的。但与您的期望值列表不匹配(例如,用户 9 仅在您的表数据中的一个组中,但您在结果中将其显示为属于两个)

      编辑:当。刚刚注意到您使用的是 MySQL。我的解决方案是针对 SQL Server。对不起。

      -- 凯文·费尔柴尔德

      【讨论】:

        【解决方案3】:

        在 SQL 标准中没有办法做到这一点,但您通常可以在 Oracle 中找到特定于供应商的扩展,例如 CONNECT BY

        更新:正如 cmets 所指出的,这是在 SQL 99 中添加的。

        【讨论】:

        • 错了。 ISO SQL 标准从 SQL:1999 标准开始就指定了递归 SQL。 DB2 和最新版本的 MSSQL 实现了它。顺便说一下,SQL 标准的递归 SQL 与 Oracle 的 CONNECT BY 完全不同。
        • 我没有意识到这一点。是否有最新标准的免费参考,因为 ISO 显然认为开发人员应该为标准本身付费?
        【解决方案4】:

        想到两件事:

        1 -您可以反复将表外部连接到自身以递归地向上遍历您的树,如下所示:

        SELECT *
        FROM
          MY_GROUPS MG1
         ,MY_GROUPS MG2
         ,MY_GROUPS MG3
         ,MY_GROUPS MG4
         ,MY_GROUPS MG5
         ,MY_GROUP_MEMBERS MGM
        WHERE MG1.PARENT_ID = MG2.UNIQID (+)
          AND MG1.UNIQID = MGM.GROUP_ID (+)
          AND MG2.PARENT_ID = MG3.UNIQID (+)
          AND MG3.PARENT_ID = MG4.UNIQID (+)
          AND MG4.PARENT_ID = MG5.UNIQID (+)
          AND MGM.USER_ID = 9
        

        这会给你这样的结果:

        UNIQID PARENT_ID NAME      UNIQID_1 PARENT_ID_1 NAME_1 UNIQID_2 PARENT_ID_2 NAME_2  UNIQID_3 PARENT_ID_3 NAME_3 UNIQID_4 PARENT_ID_4 NAME_4 UNIQID_5 GROUP_ID USER_ID
        4      2         Cerepedia 2        1           CATS   1        null        Cerebra null     null        null   null      null       null   8        4        9
        

        这里的限制是,您必须为要向上走的每个“级别”添加一个新连接。如果您的树有少于 20 个级别,那么您可以通过创建一个显示每个用户的 20 个级别的视图来摆脱它。

        2 - 我知道的唯一其他方法是创建一个递归数据库函数,并从代码中调用它。这样你仍然会有一些查找开销(即,你的查询数仍然等于你在树上行走的级别数),但总的来说它应该更快,因为它都发生在数据库中。

        我不确定 MySql,但在 Oracle 中,这样的函数将类似于此函数(您必须更改表和字段名称;我只是在复制我过去所做的一些事情):

        CREATE OR REPLACE FUNCTION GoUpLevel(WO_ID INTEGER, UPLEVEL INTEGER) RETURN INTEGER
        IS
        BEGIN
          DECLARE
            iResult INTEGER;
            iParent INTEGER;
        BEGIN
          IF UPLEVEL <= 0 THEN
            iResult := WO_ID;
          ELSE
            SELECT PARENT_ID
            INTO iParent
            FROM WOTREE
            WHERE ID = WO_ID;    
            iResult := GoUpLevel(iParent,UPLEVEL-1);  --recursive
          END;
          RETURN iResult;
          EXCEPTION WHEN NO_DATA_FOUND THEN
            RETURN NULL;
          END;
        END GoUpLevel;
        /
        

        【讨论】:

          【解决方案5】:

          Joe Cleko 的书“SQL for Smarties”和“Trees and Hierarchies in SQL for Smarties”描述了通过使用嵌套集完全避免递归的方法。这使更新复杂化,但使其他查询(通常需要递归)相对简单。有乔在 1996 年写的some examples in this article

          【讨论】:

            【解决方案6】:

            已经提出了类似的question

            这是我的回答(稍作修改):

            我不确定我是否正确理解了您的问题,但这可能有效My take on trees in SQL

            链接的帖子描述了在数据库中存储树的方法——在这种情况下是 PostgreSQL——但该方法足够清晰,因此可以轻松地用于任何数据库。

            使用这种方法,您可以使用大约 N 个简单的 SELECT 查询轻松更新所有依赖于修改节点 K 的节点,其中 N 是 K 到根节点的距离。

            祝你好运!

            【讨论】:

              【解决方案7】:

              我不记得我在哪个 SO 问题下找到了链接,但 this article on sitepoint.com(第二页)显示了另一种在表中存储分层树的方法,可以轻松找到所有子节点或指向顶,诸如此类。示例代码很好的解释。


              附言。 StackOverflow 的新手,以上内容可以作为答案吗,或者它真的应该是对问题的评论,因为它只是指向不同解决方案的指针(不完全回答问题本身)?

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-11-09
                • 2010-11-22
                • 2018-03-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多