Storing Hierarchical Data (MySQL) for Referral Marketing
我需要为注册到网站的用户提供5级层次结构。每个用户都被另一个用户邀请,我需要知道用户的所有后代。也是用户的祖先。
我想到了2个解决方案。
以这种方式保持关系表。关闭表:
1 2 3 4 5 6 7 8
| ancestor_id descendant_id distance
1 1 0
2 2 0
3 3 0
4 4 0
5 5 0
6 6 0
2 3 1 |
有这个关系表。保持在一个表5级祖先。一个"祖先"表:
1 2 3
| user_id ancestor_level1_id ancestor_level2_id ancestor_level3_id ancestor_level4_id ancestor_level5_id
10 9 7 4 3 2
9 7 4 3 2 1 |
这些好主意吗?
我知道"邻接列表模型"和"修改后的预订树遍历算法",但这些是"推荐"系统的良好解决方案吗?
我需要在这棵树上执行的查询是:
-
经常添加新用户
-
当用户购买东西时,他们的推荐人获得百分比佣金
-
每个用户都应该能够在每个级别找出他们推荐了多少人(以及他们推荐的人推荐了多少人)
-
如果您定义"好"会有所帮助 - 您是否在寻求速度,灵活性和易维护性?
-
@Neville我正在寻找速度,易于维护但也有一些灵活性
-
@Neville,@ morandi3:我们需要确切地知道你想要执行什么类型的查询。
关闭表
1 2 3 4 5 6 7 8
| ancestor_id descendant_id distance
1 1 0
2 2 0
3 3 0
4 4 0
5 5 0
6 6 0
2 3 1 |
添加用户3引用的用户10.(我认为您不需要在这两个插入之间锁定表):
查找用户3引用的所有用户。
要按深度计算这些用户:
找到用户10的祖先。
此方法的缺点是此表将占用的存储空间量。
-
你所有的解决方案看起来都很好:D很难找到最好的解决方案。我想我现在会选择这个。但OQGRAPH存储引擎似乎也是一个可靠的解决方案。谢谢
使用OQGRAPH存储引擎。
您可能希望跟踪任意数量的级别,而不仅仅是5个级别。获取一个支持QGRAPH引擎的MySQL分支(例如MariaDB或OurDelta),并使用它来存储您的树。它实现了邻接列表模型,但是通过使用一个名为latch的特殊列向存储引擎发送命令,告诉它要执行什么样的查询,您可以获得闭包表的所有优点而无需执行每次有人为您的网站注册时的簿记工作。
以下是您在OQGRAPH中使用的查询。请参阅文档
http://openquery.com/graph-computation-engine-documentation
我们将使用origid作为引用者,并将destid作为引用。
添加用户11引用的用户11
查找用户3引用的所有用户。
找到用户10的祖先。
要查找用户3引用的每个级别的用户数:
-
@Ken我不知道保持"任意"数量的水平是否是一个好主意,让我们说我们将有大约100个级别。将所有这些级别保留在数据库中是否是一个好的想法?
-
@ morandi3:只关注技术限制(不讨论隐私问题),这取决于你是如何做到的。您的祖先表使用的存储空间与您要跟踪的最大级别数成比例。 OQGRAPH是一种专门用于执行图算法的存储引擎。它被设计用于执行像Dijkstra的最短路径算法这样的操作,这些算法在SQL数据库中通常很难或不可能,通过在表中使用特殊列来向存储引擎发出命令。它没有相同的空间惩罚。
-
@Ken我不确定我可以在我的服务器上使用MariaDB或OurDelta。是否可以将此引擎仅用于数据库中的表,或者我需要更改所有表的存储?我的哪些想法在您的选择中看起来最快/最可靠?
-
@ morandi3:您不需要为所有表更改存储空间。 MySQL中的存储引擎是一个用于创建和管理特定类型表的插件。 MySQL中的默认表类型是MyISAM,默认分发版还允许您使用InnoDB存储引擎创建特定表(以便在事务中实现更好的并发性)。 OQGRAPH只是您可以在需要时创建的另一种表。它不会更改默认值,也不会更改用于现有表的存储引擎,也不会替换默认存储引擎。
-
@ morandi3:要知道什么是最好的,我需要知道你想要执行什么类型的查询。我认为嵌套集模型不适合你,因为你有很多插入。如果您正在强烈考虑5级关系表,则可能会简化使用分隔的祖先字符串而不是5个单独列的事情。
-
@Ken我们将查询:添加新用户,当后代购买东西时给予祖先佣金(百分比),每个用户都可以知道每个级别有多少后代(为此我想使用一些字段) database:count_level1,count_level2,保存这些信息的地方。是的,当我需要查找祖先时,分隔的祖先字符串应该没问题,但我还需要为每个用户找到后代。
-
@ morandi3:我现在有2个答案,详细说明了如何处理每个查询。这些模型都不是很复杂。
-
@Ken谢谢肯
-
@ morandi3:点击答案旁边的复选标记,不要忘记接受你决定实际使用的答案。
在MySQL中管理分层数据
一般来说,我喜欢"嵌套",尤其是在MySQL中,它实际上没有对分层数据的语言支持。
这很快,但如果易于维护是一件大事,你需要确保你的开发人员阅读那篇文章。它非常灵活 - 在您的情况下似乎并不重要。
它似乎非常适合您的问题 - 在推荐模型中,您需要找到引用树,这在嵌套集模型中很快;你还需要知道给定用户的@ children @是谁以及他们关系的深度;这也很快。
-
我认为这对我的系统来说不是一个好方法,因为每次新用户注册时都需要更新嵌套集,不是吗?!
划界的祖先串
如果您正在强烈考虑5级关系表,则可能会简化使用分隔的祖先字符串而不是5个单独列的事情。
1 2 3 4 5 6
| user_id depth ancestors
10 7 9,7,4,3,2,1
9 6 7,4,3,2,1
...
2 2 1
1 1 (empty string) |
以下是您使用此模型的一些SQL命令:
添加用户11引用的用户11
查找用户3引用的所有用户。(请注意,此查询不能使用索引。)
要查找用户10的祖先。您需要在客户端程序中分解字符串。在Ruby中,代码是ancestorscolumn.split(",").map{|x| x.to_i}。在SQL中分解字符串没有好办法。
要查找用户3引用的每个级别的用户数:
您可以通过使用like concat('%,', ?, ',%')而不是将用户编号的整数绑定到占位符来避免这些查询的like '%,3,%'部分中的SQL注入攻击。
-
@Ken"祖先"字段不应该是这种情况下的"后代"?或者我们使用"深度"?因为每个用户在每个级别只有一个祖先。
-
@ morandi3:在最后一个查询中,您正在寻找"具有3作为祖先的节点",并将它们分组到它们所在的节点3之下。 depth列跟踪某个节点有多少祖先,将它们分组到数据库中的级别。
-
@Ken是的,但用户只有一个祖先/级别
-
@ morandi3:我认为你让父母和祖先混淆了。用户10只有一个父母(用户9)。并且用户9具有一个父母(用户7)。用户7还具有一个父(用户4)。所有这些父母和父母的父母都被称为用户10的"祖先"。因此,深度是指在您找到未被朋友推荐的人(找到您网站的人)之前必须经过的跳数通过广告或随机搜索)。
-
@Ken好的,现在我明白了,深度是链条的长度。嗯,但是这个解决方案很难为用户找到后代。
-
@morandi:找到后代是第二个查询。 (请记住,就像祖先一样,后代是儿童和孩子的孩子等等)你的意思是找到孩子吗?
-
@Ken我想说的是:找到特定级别的后代将会很慢,因为我们不能使用索引
-
@ morandi3:嗯,是的。这是该模型的缺点。 (您可以在depth字段上放置一个索引,但我怀疑这会有多大帮助。)这就是为什么SQL和图形/树不会混合,简而言之,以及OQGRAPH存储引擎的发明原因。
-
@Ken嗯,是的,OQGRAPH似乎是一个很好的解决方案。你认为一个闭包表(ancestor_id descendant_id距离)还是使用5个字段,每个级别一个会减慢到什么程度?
-
@ morandi3:我认为使用5个字段将非常难以使用。我会发布一个关于如何使用闭包表做事的答案,然后你可以决定是否可行。
-
@ morandi3:如果你不担心多次列出每个祖先的空间爆炸,那么封闭表实际上看起来是可行的。
-
@Ken:我有点担心,因为对于50.000个用户,我们将有一个大约300.000行的表