Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/83.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
MySQL中的递归组结构_Mysql_Sql_Recursion - Fatal编程技术网

MySQL中的递归组结构

MySQL中的递归组结构,mysql,sql,recursion,Mysql,Sql,Recursion,我正在开发一个需要允许用户分组的系统。这些组可以由系统中的其他特权用户自由创建、编辑和删除。那部分很容易;只需创建一个group\u users表,将用户链接到组中。(如果你坚持规范化,那么你可以创建一个只列出组的组表,然后有一个组用户表将它们链接在一起——这也很好) 这就是它变得棘手的地方。客户端希望组也包含任意深度和任意重叠的组(组可以在多个组中,组可以包含多个组)。这很容易存储(使用group\u groups表),但如果没有像Oracle的CONNECT BY这样的排序扩展,就很难进行查

我正在开发一个需要允许用户分组的系统。这些组可以由系统中的其他特权用户自由创建、编辑和删除。那部分很容易;只需创建一个
group\u users
表,将用户链接到组中。(如果你坚持规范化,那么你可以创建一个只列出组的
表,然后有一个
组用户
表将它们链接在一起——这也很好)

这就是它变得棘手的地方。客户端希望组也包含任意深度和任意重叠的组(组可以在多个组中,组可以包含多个组)。这很容易存储(使用
group\u groups
表),但如果没有像Oracle的CONNECT BY这样的排序扩展,就很难进行查询

这个递归层次结构也需要追溯——这意味着,如果组A包含组B,并且组B被修改,那么组A也将被修改——所以我不能欺骗,只是将结构展平。如果你不相信我,它不能简单地变平,考虑一下这种情况。您有一个名为“酷人”的组,其中包含用户1和2。有人创建了一个名为“真正酷的人”的组,其中包含用户3和组“酷的人”。当我询问“非常酷的人”时,我应该得出结论,用户1、2和3都在组中。现在,假设有人认为用户2不再是一个酷的人,并将用户2从“酷的人”中删除。在此时间点之后,“真正酷的人”只包含用户1和3。如果我最初将结构展平,当我将用户2从“酷人”中删除时,我不知道如何将其从“真酷人”中删除

因此,在这种情况下,简单的平坦化是不起作用的。我考虑过的其他选择:

  • 在代码中执行递归。
    • 对于复杂的组来说速度太慢,并且还需要在内存中而不是在数据库中进行相关连接
  • 将结构展平为
    group\u users\u展平
    ,但同时维护一个
    group\u groups
    表。在插入/更新/删除时为
    group\u users\u planted
    创建触发器,该触发器将转到
    group\u groups
    表,查找包含此组的所有组,并动态对
    group\u users\u planted
    进行适当更改。
    • 我可以想象这是可行的,但是它看起来很复杂而且容易出错,我有一种感觉,有一种我看不到的感觉

还有其他我没有考虑的想法吗?

我会使用嵌套集。详情如下:


虽然我从来没有用它来表示重叠。

您能有一个用户组表(每行有一列,用于区分用户项和组项)和一个单独的多连接表,列出所有用户组成员资格吗

我猜连接表需要一个约束来确保groups列既是第一个表中的FK,又是group类型。(换句话说,如果连接表有两列:member_ID和group_ID,则member_ID可以是对成员或组的引用,而group_ID只能引用组

这将允许任何用户或组成为任何组的成员,同时防止任何用户或组成为用户的“成员”


(顺便说一句:我对MySQL还不够精通,现在还不足以编写一个工作示例;但我想看看这个建议是否可行)。

这样的结构怎么样


一种关系,例如真正的酷人与酷人之间的关系是“链接的”(因此是适当的级联),反之亦然,是免费的。

您是否考虑过组表中的自引用结构?例如,您在一个名为“超类”的列中添加了自引用结构就像OOP一样,子类继承自超类。然后给它一个ID列,这样您就有:

[ID |组名|任何其他列|超类]

以及ID和超类之间的外键约束

这样,假设您有一个组heffatlump,ID=3。它的超类可以是1,其中ID为1对应于组名winniethepooh

假设Woozle的ID为4。它也可以有超类1。所以它仍然在winniethepooh下

相当简单,但它应该不会有太多麻烦。这样,根据您的示例,“真正酷的人”将在“酷的人”下分级,因此“真正酷的人”中唯一不在“酷的人”中的人将是那些一开始就不在“酷的人”中的人。因此,如果您将一个人从“酷的人”中删除他不会被归入“非常酷的人”,但如果你把一个人从“非常酷的人”中剔除,这不会影响“非常酷的人”

对不起,解释得太复杂了,我希望这能有所帮助

*编辑:我注意到这基本上是另一个链接中给出的第一个示例。在这种情况下,我没有其他想法了。对不起!

请参见我的答案。我描述了一个我称之为闭包表的设计

在您的情况下,您将拥有表
Users
Groups
UserGroupMembers
,这是用户和组之间的交叉表(多对多)

然后,您需要另一个表来描述组是如何嵌套的。例如,将其称为
subsubpath
。该表记录给定组与其子组相关的每条路径

CREATE TABLE SubgroupPaths (
  supergroup INT UNSIGNED NOT NULL,
  subgroup   INT UNSIGNED NOT NULL,
  pathlength SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (supergroup, subgroup),
  FOREIGN KEY (supergroup) REFERENCES Groups(groupid),
  FOREIGN KEY (subgroup) REFERENCES Groups(groupid)
);
您可能还需要一些复合索引排列来支持针对该表运行的某些查询

这种设计允许您拥有多个层次结构,因此您可以让组“酷人”成为其每个超级组的后代

INSERT INTO Groups (groupid, groupname) VALUES
(1, 'REALLY cool people'),
(2, 'slightly cool people'),
(3, 'cool people');

INSERT INTO SubgroupPaths (supergroup, subgroup, pathlength) VALUES
(1,1,0), (2,2,0), (3,3,0), -- every group points to itself
(1,3,1), -- REALLY is parent of cool people
(2,3,1); -- slightly is also parent of cool people
现在你可以得到所有应该被认为是酷人的用户的列表,不管他们是否是酷人,稍微酷一点的人的成员
SELECT DISTINCT u.*
FROM SubgroupPaths AS cool
JOIN SubgroupPaths AS supercool ON cool.subgroup=supercool.subgroup
JOIN Groups AS g ON supercool.supergroup = g.groupid
JOIN UserGroupMembers AS m ON m.groupid = g.groupid
JOIN Users AS u ON u.userid = m.userid
WHERE cool.subgroup = 3;
WITH Groups AS
(
   --initialization
   SELECT ParentGroups.GroupID, ParentGroups.GroupName, ParentGroups.ParentGroupID
   FROM ParentGroups
   WHERE ParentGroups.ParentGroupID IS NULL
   UNION ALL
   --recursive execution
   SELECT SubGroups.GroupID, SubGroups.GroupName, SubGroups.ParentGroupID
   FROM Groups SubGroups INNER JOIN Groups ParentGroups 
   ON SubGroups.ParentGroupID = ParentGroups.GroupID
)
SELECT * FROM Groups