我正在开发一个需要允许用户分组的系统。系统中的其他特权用户可以自由创建、编辑和删除这些组。这一部分很容易;只需创建一个group_users
表,将用户链接到组中。(如果你是一个坚持标准化的人,那么你可以创建一个group
表,只列出组,然后创建一个将它们链接在一起的group_users
表格——这也很好)
这就是问题的症结所在。客户端希望组也包含任意深度和任意重叠的组(组可以在多个组中,组可以包含多个组)。这很容易存储(使用group_groups
表),但如果没有像Oracle的CONNECT BY这样的排序扩展,就很难查询。
这个递归层次结构还需要具有追溯性——这意味着如果组A包含组B,并且组B被修改,那么组A也将被修改——所以我不能作弊,只需将结构扁平化。如果你不相信我,它不能简单地被压平,考虑一下这种情况。你有一个名为“酷人”的组,其中包含用户1和2。有人创建了一个名“真正酷人”组,其中包括用户3,并包含组“酷人。”。当我询问“真正酷的人”时,我应该得出结论:用户1、2和3在组中。现在假设有人认为用户2不再是一个酷的人,并将用户2从“酷的人”中删除。在那个时间点之后,“真正酷的人”只包含用户1和3。如果我最初将结构扁平化,当我将用户2从“酷的人(cool people)”中删除时,我不知道如何将其从“真正酷”中删除。
因此,在这种情况下,一个微不足道的平坦化是行不通的。我考虑过的其他选项:
- 在代码中执行递归。
- 对于复杂的组来说太慢了,而且还需要在内存中而不是在数据库中执行相关连接
- 将结构扁平化为
group_users_flattened
,但还要维护group_groups
表。在INSERT/UPDATE/DELETE上为group_users_flattened
创建一个触发器,该触发器将转到group_groups
表,找到包含该组的所有组,并动态地对group_users_flattened
进行适当的更改。 - 我可以想象这是可行的,但它看起来很复杂,容易出错,我觉得有一个我没有看到的问题。
还有其他我没有考虑过的想法吗?
7条答案
按热度按时间bvk5enib1#
请参阅我对“将平面表解析为树的最有效/最优雅的方法是什么?”的回答?。我描述了一种我称之为“闭包表”的设计。
在您的示例中,您将拥有表
Users
、Groups
和UserGroupMembers
,这是用户和组之间的交集表(多对多)。然后需要另一个表来描述组是如何嵌套的。例如,称其为
SubgroupPaths
。这将记录将给定组与其子组关联的每条路径。您可能还需要一些复合索引的排列来支持对该表运行的某些查询。
这种设计允许您有多个层次结构,因此您可以让组“酷人”成为其每个超级组的后代。
现在你可以得到所有应该被认为是酷人的用户的列表,不管他们是酷人、稍微酷的人还是真正酷的人。我们甚至可以添加一个DISTINCT,以防某些用户与这些组中的多个关联。
我更喜欢闭包表,而不是其他答案建议的嵌套集设计,因为闭包表支持引用完整性约束,并且有一些查询在嵌套集中很难,但在闭包表中更容易。
有关闭包表的更多信息,请参阅我的书SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming。
所有这些的脚注:小心违反YAGNI原则。
我曾经实现了一个数据库来存储这样的任意深度组,以及用于显示、报告和管理层次结构的所有PHP代码。此外,我必须在使用分层集合时克隆它们,因为稍后可以重新组织层次结构,并且我们需要保留层次结构中元素如何使用的历史数据。编码和测试花费了数周时间。当这一切都完成后,应用程序的用户实际上从未存储过任何层次结构,只有一层深。
如果你告诉一些决策者实施和测试需要做多少工作(即预算),他们会改变对需求范围的看法。
yhxst69z2#
使用嵌套集的查询可能比使用存储过程遍历邻接列表的查询更快,对于缺少本地递归查询结构(如MySQL)的数据库来说,也是更快的选择
http://en.wikipedia.org/wiki/Nested_set_model升
https://docs.joomla.org/Using_nested_sets
然而,插入新节点(行)将需要相应地更新所有行
tzdcorbm3#
我会使用嵌套集。此处提供详细信息:
http://www.alandelevie.com/2008/07/12/recursion-less-storage-of-hierarchical-data-in-a-relational-database/升
虽然我从未用它来表示重叠。
epggiuax4#
您能有一个users_groups表(每行有一列,以区分用户条目和组条目)和一个单独的多连接表,列出所有user_group_memberships吗?
我猜想连接表需要一个约束来确保groups列是第一个表的FK,并且类型是group。(换句话说,如果连接表有两列:member_ID和group_ID,则member_ID可以是对成员或组的引用,而group_ID只能引用组。
这将允许任何用户或组被包括在任何组的成员中,同时防止任何用户或用户组成为用户的“成员”。
(顺便说一句:我对MySQL还不够精通,现在还没有准备好一个工作示例;但如果这个建议可行,我希望看到一个)。
7d7tgy0s5#
这样的结构怎么样
小时
一种关系,比如真正酷的人与酷的人之间的关系是“连锁”的(因此是适当的级联),反之亦然。
ergxz8rk6#
您是否考虑过分组表中的自参照结构?假设你放了一个名为“超类”的列。就像OOP一样,子类继承自超类。然后给它一个ID列,这样你就可以:
[ID|组名称|任何其他列|超类]
以及ID和超类之间的外键约束。
这样,假设您有组heffalump,ID=3。它的超类可以是1,其中ID=1对应于组名winniethepooh。
假设Woozle的ID为4。它也可以有超类1。所以它仍然处于winniethepooh之下。
相当简单,但应该不会有太多麻烦。这样,按照你的例子,“真正酷的人”将被划分为“酷的人(cool people)”之下的等级,所以“真正酷”中唯一不属于“酷的”的人将是那些一开始就不属于“冷的人”的人。因此,如果你把一个人从“酷人”中剔除,他就不会被归类为“真正酷的人”,但如果你把他从“真正酷人”里剔除,它不会影响“酷的人。”
抱歉解释得太复杂了,我希望这能有所帮助!
uidvcgyl7#
我将研究使用公共表表达式(CTE)进行递归。根据我的经验,这是在SQLServer中查询分层数据的最有效方法。
以下链接解释了如何使用CTE:http://msdn.microsoft.com/en-us/library/ms190766.aspx
下面是一个简单的示例,说明如何使用CTE查询层次结构。显然,您必须调整应用程序的代码,但这应该为您指明正确的方向。
此外,您不需要有group_groups表。通过添加ParentGroupID列,可以在组表中保留整个层次结构。