关于数据库:为推荐营销存储分层数据(MySQL)

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

    这些好主意吗?

    我知道"邻接列表模型"和"修改后的预订树遍历算法",但这些是"推荐"系统的良好解决方案吗?

    我需要在这棵树上执行的查询是:

    • 经常添加新用户
    • 当用户购买东西时,他们的推荐人获得百分比佣金
    • 每个用户都应该能够在每个级别找出他们推荐了多少人(以及他们推荐的人推荐了多少人)


    关闭表

    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.(我认为您不需要在这两个插入之间锁定表):

    1
    2
    3
    4
    5
    6
    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引用的所有用户。

    1
    select descendant_id from ancestor_table where ancestor_id=3;

    要按深度计算这些用户:

    1
    select distance, count(*) from ancestor_table where ancestor_id=3 group by distance;

    找到用户10的祖先。

    1
    select ancestor_id, distance from ancestor_table where descendant_id=10;

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


    使用OQGRAPH存储引擎。

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

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

    我们将使用origid作为引用者,并将destid作为引用。

    添加用户11引用的用户11

    1
    insert into ancestors_table (origid,destid) values (10,11)

    查找用户3引用的所有用户。

    1
    SELECT linkid FROM ancestors_table WHERE latch = 2 AND origid = 3;

    找到用户10的祖先。

    1
    SELECT linkid FROM ancestors_table WHERE latch = 2 AND destid = 10;

    要查找用户3引用的每个级别的用户数:

    1
    2
    3
    4
    SELECT count(linkid), weight
    FROM ancestors_table
    WHERE latch = 2 AND origid = 3
    GROUP BY weight;


    在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

    1
    2
    3
    4
    insert into ancestors_table (user_id, depth, ancestors)
    select 11, depth+1, concat(10,',',ancestors)
    from ancestors_table
    where user_id=10;

    查找用户3引用的所有用户。(请注意,此查询不能使用索引。)

    1
    2
    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中分解字符串没有好办法。

    1
    select ancestors from ancestors_table where user_id=10;

    要查找用户3引用的每个级别的用户数:

    1
    2
    3
    4
    5
    6
    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注入攻击。