我需要为注册到网站的用户提供5级层次结构。每个用户都被另一个用户邀请,我需要知道用户的所有后代。也是用户的祖先。
我想到了2个解决方案。
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
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
这些好主意吗?
我知道“邻接列表模型”和“修改后的预订树遍历算法”,但这些是“推荐”系统的良好解决方案吗?
我需要在此树上执行的查询是:
答案 0 :(得分: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.(我不认为你需要在这两个插入之间锁定表):
insert into ancestor_table
select ancestor_id, 10, distance+1
from ancestor_table
where descendant_id=3;
insert into ancestor_table values (10,10,0);
查找用户3引用的所有用户。
select descendant_id from ancestor_table where ancestor_id=3;
按深度计算这些用户:
select distance, count(*) from ancestor_table where ancestor_id=3 group by distance;
查找用户10的祖先。
select ancestor_id, distance from ancestor_table where descendant_id=10;
此方法的缺点是此表将占用的存储空间量。
答案 1 :(得分:4)
您可能希望跟踪任意级别,而不仅仅是5个级别。获取一个支持QGRAPH engine的MySQL分支(例如MariaDB或OurDelta),并使用它来存储您的树。它实现了邻接列表模型,但是通过使用名为latch
的特殊列向存储引擎发送命令,告诉它要执行哪种查询,您可以获得闭包表的所有优点而无需每次有人为您的网站注册时都会进行簿记工作。
以下是您在OQGRAPH中使用的查询。请参阅文档 http://openquery.com/graph-computation-engine-documentation
我们将使用origid作为推荐人,并将destid用作推荐人。
添加用户10引用的用户11
insert into ancestors_table (origid,destid) values (10,11)
查找用户3引用的所有用户。
SELECT linkid FROM ancestors_table WHERE latch = 2 AND origid = 3;
查找用户10的祖先。
SELECT linkid FROM ancestors_table WHERE latch = 2 AND destid = 10;
要查找用户3引用的每个级别的用户数:
SELECT count(linkid), weight
FROM ancestors_table
WHERE latch = 2 AND origid = 3
GROUP BY weight;
答案 2 :(得分:2)
Managing Hierarchical Data in MySQL
一般来说,我喜欢“嵌套”,尤其是在MySQL中,它实际上没有对分层数据的语言支持。 这很快,但如果易于维护是一件大事,你需要确保你的开发人员阅读那篇文章。它非常灵活 - 在您的情况下似乎并不重要。
它似乎非常适合您的问题 - 在推荐模型中,您需要找到引用树,这在嵌套集模型中很快;你还需要知道给定用户的@ children @是谁以及他们关系的深度;这也很快。
答案 3 :(得分:1)
如果你正在强烈考虑5级关系表,它可以简化使用分隔的祖先字符串而不是5个单独的列的事情。
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命令:
添加用户10引用的用户11
insert into ancestors_table (user_id, depth, ancestors)
select 11, depth+1, concat(10,',',ancestors)
from ancestors_table
where user_id=10;
查找用户3引用的所有用户。(请注意,此查询不能使用索引。)
select user_id
from ancestors_table
where ancestors like '%,3,%' or ancestors like '3,%' or ancestors like '%,3';
查找用户10的祖先。您需要在客户端程序中分解字符串。在Ruby中,代码为ancestorscolumn.split(",").map{|x| x.to_i}
。在SQL中分解字符串没有好办法。
select ancestors from ancestors_table where user_id=10;
要查找用户3引用的每个级别的用户数:
select
depth-(select depth from ancestors_table where user_id=3),
count(*)
from ancestors_table
where ancestors like '%,3,%' or ancestors like '3,%' or ancestors like '%,3'
group by depth;
通过使用like '%,3,%'
代替并将用户编号的整数绑定到占位符,可以避免这些查询的like concat('%,', ?, ',%')
部分中的SQL注入攻击。