【问题标题】:Storing Hierarchical Data (MySQL) for Referral Marketing为推荐营销存储分层数据 (MySQL)
【发布时间】:2023-03-31 06:10:01
【问题描述】:

我需要为注册到网站的用户设置 5 级层次结构。每个用户都被另一个用户邀请,我需要知道一个用户的所有后代。也是用户的祖先。

我想到了 2 个解决方案。

  1. 以这种方式保存具有关系的表。闭包表:

    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
  1. 有这个关系表。在表中保存 5 个级别的祖先。 “祖先”表:

   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:我们需要确切地知道你想要执行什么样的查询。

标签: mysql database hierarchy


【解决方案1】:

Managing Hierarchical Data in MySQL

总的来说,我喜欢“嵌套集”,尤其是。在 MySQL 中,它实际上并没有对分层数据的语言支持。 它很快,但如果易于维护很重要,您需要确保您的开发人员阅读该文章。它非常灵活 - 在您的情况下似乎并不重要。

这似乎很适合您的问题——在推荐模型中,您需要找到推荐者树,这在嵌套集合模型中很快;您还需要知道给定用户的 ~children@ 是谁,以及他们的关系深度;这也很快。

【讨论】:

  • 我认为这对我的系统来说不是一个好方法,因为每次新用户注册时都需要更新嵌套集,不是吗?!
【解决方案2】:

使用 OQGRAPH 存储引擎。

您可能希望跟踪 任意 个级别,而不仅仅是 5 个级别。获取支持QGRAPH engine 的 MySQL 分支之一(例如 MariaDB 或 OurDelta),并使用它来存储您的树。它实现了邻接表模型,但是通过使用名为latch 的特殊列向存储引擎发送命令,告诉它要执行什么样的查询,您可以获得闭包表的所有优点,而无需执行每次有人注册您的网站时都会进行簿记。

以下是您将在 OQGRAPH 中使用的查询。请参阅文档 http://openquery.com/graph-computation-engine-documentation

我们将使用 origid 作为推荐人,使用 destid 作为推荐人。

添加用户 11,由用户 10 推荐

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;

【讨论】:

  • @Ken 我不知道保持“任意”数量的级别是否是个好主意,假设我们将有大约 100 个级别。将所有这些级别都保存在数据库中是个好主意吗?
  • @morandi3:只看技术限制(不讨论隐私影响),这取决于你是怎么做的。 您的祖先表使用与您正在跟踪的最大级别数成比例的存储空间。 OQGRAPH 是一个专门用于执行图形算法的存储引擎。它旨在执行像 Dijkstra 的最短路径算法这样的操作,这些操作在 SQL 数据库中通常是困难的或不可能的,方法是在表中有一个特殊的列来向存储引擎发出命令。它没有相同的空间损失。
  • @Ken 我不确定我是否可以在我的服务器上使用 MariaDB 或 OurDelta。是否可以将此引擎仅用于数据库中的表,或者我需要更改所有表的存储?在你看来,我的哪个想法看起来最快/最可靠?
  • @morandi3:您不需要更改所有表格的存储空间。 MySQL 中的存储引擎是用于创建和管理特定类型表的插件。 MySQL 中的默认表类型是 MyISAM,默认分布还允许您使用 InnoDB 存储引擎创建特定的表(以实现更好的事务并发性)。 OQGRAPH 只是另一种表,您可以在需要时创建它。它不会更改默认值,不会更改用于现有表的存储引擎,也不会替换默认存储引擎。
  • @morandi3:要知道什么是最好的,我需要知道你想要执行什么样的查询。我不认为嵌套集合模型对你很有效,因为你有很多插入。如果您强烈考虑使用 5 级关系表,则使用分隔的祖先字符串而不是 5 个单独的列可能会简化事情。
【解决方案3】:

祖先的分隔字符串

如果您强烈考虑使用 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 命令:

添加用户 11,由用户 10 推荐

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 concat('%,', ?, ',%') 并将用户编号的整数绑定到占位符来避免这些查询的like '%,3,%' 部分中的SQL 注入攻击。

【讨论】:

  • @Ken“祖先”字段在这种情况下不应该是“后代”吗?还是我们使用“深度”?因为每个用户在每个级别只有一个祖先。
  • @morandi3:在最后一个查询中,您正在寻找“以 3 为祖先的节点”,并将它们按低于节点 3 的距离进行分组。 depth 列跟踪某个节点有多少祖先,并将它们分组到数据库中的级别。
  • @Ken 是的,但是一个用户只有一个祖先/级别
  • @morandi3:我认为您将 parentancestor 混淆了。用户 10 只有一个 parent(用户 9)。并且用户 9 有一个父母(用户 7)。用户 7 也有一个父母(用户 4)。所有这些父母和父母的父母都被称为用户 10 的“祖先”。因此,深度是在您到达不是由朋友推荐的人(找到您的网站的人)之前必须经过的跳数通过广告或随机搜索)。
  • @Ken 好的,现在我明白了,深度是链的长度。嗯,但是使用此解决方案很难为用户找到后代。
【解决方案4】:

闭包表

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

要添加用户 10,由用户 3 引用。(我不认为您需要在这两个插入之间锁定表):

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;

此方法的缺点是此表将占用大量存储空间。

【讨论】:

  • 你所有的解决方案看起来都不错 :D 很难找到最好的。我想我现在会选择这个。但 OQGRAPH 存储引擎似乎也是一个可靠的解决方案。谢谢
猜你喜欢
  • 1970-01-01
  • 2021-09-02
  • 2011-01-31
  • 2017-11-26
  • 2011-11-09
  • 1970-01-01
  • 1970-01-01
  • 2010-10-16
  • 2010-11-14
相关资源
最近更新 更多