在MySQL中处理嵌套集合?

7
我决定跟随http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html,现在我正在寻求代码方面的帮助。
我正在使用他们的数据进行测试,所以我将树形结构可视化如下:
    array('value' => 'Richard Shakespeare',
        array('value' => 'Henry',
            array('value' => 'Joan'),
            array('value' => 'Margaret'),
            array('value' => 'William',
                array('value' => 'Susana',
                    array('value' => 'Elizabeth Hall',
                        array('value' => 'John Bernard'))),
                array('value' => 'Hamnet'),
                array('value' => 'Judith',
                    array('value' => 'Shakespeare Quiney'),
                    array('value' => 'Richard Quiney'),
                    array('value' => 'Thomas Quiney'))),
            array('value' => 'Gilbert'),
            array('value' => 'Joan',
                array('value' => 'William Hart'),
                array('value' => 'Mary Hart'),
                array('value' => 'Thomas Hart'),
                array('value' => 'Micheal Hart')),
            array('value' => 'Anne'),
            array('value' => 'Richard'),
            array('value' => 'Edmond')),
        array('value' => 'John'));

所以,如果我们想将其插入到数据库中,我们希望最终得到的是

Array
(
    [0] => Array
        (
            [value] => Richard Shakespeare
            [left] => 1
            [right] => 46
        )

    [1] => Array
        (
            [value] => Henry
            [left] => 2
            [right] => 43
        )

    [2] => Array
        (
            [value] => Joan
            [left] => 3
            [right] => 4
        )

    [3] => Array
        (
            [value] => Margaret
            [left] => 5
            [right] => 6
        )

    [4] => Array
        (
            [value] => William
            [left] => 7
            [right] => 24
        )

    [5] => Array
        (
            [value] => Susana
            [left] => 8
            [right] => 13
        )

    [6] => Array
        (
            [value] => Elizabeth Hall
            [left] => 9
            [right] => 12
        )

    [7] => Array
        (
            [value] => John Bernard
            [left] => 10
            [right] => 11
        )

    [8] => Array
        (
            [value] => Hamnet
            [left] => 14
            [right] => 15
        )

    [9] => Array
        (
            [value] => Judith
            [left] => 16
            [right] => 23
        )

    [10] => Array
        (
            [value] => Shakespeare Quiney
            [left] => 17
            [right] => 18
        )

    [11] => Array
        (
            [value] => Richard Quiney
            [left] => 19
            [right] => 20
        )

    [12] => Array
        (
            [value] => Thomas Quiney
            [left] => 21
            [right] => 22
        )

    [13] => Array
        (
            [value] => Gilbert
            [left] => 25
            [right] => 26
        )

    [14] => Array
        (
            [value] => Joan
            [left] => 27
            [right] => 36
        )

    [15] => Array
        (
            [value] => William Hart
            [left] => 28
            [right] => 29
        )

    [16] => Array
        (
            [value] => Mary Hart
            [left] => 30
            [right] => 31
        )

    [17] => Array
        (
            [value] => Thomas Hart
            [left] => 32
            [right] => 33
        )

    [18] => Array
        (
            [value] => Micheal Hart
            [left] => 34
            [right] => 35
        )

    [19] => Array
        (
            [value] => Anne
            [left] => 37
            [right] => 38
        )

    [20] => Array
        (
            [value] => Richard
            [left] => 39
            [right] => 40
        )

    [21] => Array
        (
            [value] => Edmond
            [left] => 41
            [right] => 42
        )

    [22] => Array
        (
            [value] => John
            [left] => 44
            [right] => 45
        )

)

那么问题来了,如何最好地做到这一点?

我的解决方案是:

$container = array();

function children($item){
  $children = 0;
  foreach($item as $node)
    if(is_array($node))
      $children += children($node)+1;
    return $children;
}

function calculate($item, &$container, $data = array(0,0)){
  //althought this one is actually of no use, it could be useful as it contains a count 
  $data[0]++; //$left

  $right = ($data[0]+(children($item)*2))+1;

  //store the values in the passed container
  $container[] = array(
    'value' => $item['value'],
    'left'  => $data[0],
    'right' => $right,
  );

  //continue looping
  $level = $data[1]++;
  foreach($item as &$node)
    if(is_array($node))
      $data = calculate($node, $container, $data);

    $data[1] = $level;
    $data[0]++;
    return $data;
}

calculate($tree, $container);

我不知道它有多有效率。
现在进入查询。
要选择一个节点的所有后代,我们可以使用:
SELECT child.value AS 'Descendants of William', COUNT(*) AS `Level`
FROM tester AS parent
JOIN tester AS child ON child.`left` BETWEEN parent.`left` AND parent.`right`
WHERE parent.`left` > 7 AND parent.`right` < 24
GROUP BY child.value ORDER BY `level`;

要选择节点的所有后代,到特定深度我们可以使用
请注意,我们选择William的后代,深度为2
William的左侧:7,William的右侧:24,级别:2
SELECT child.value AS 'Descendants of William', COUNT(*) AS `Level`
FROM tester AS parent
JOIN tester AS child ON child.`left` BETWEEN parent.`left` AND parent.`right`
WHERE parent.`left` > 7 AND parent.`right` < 24
GROUP BY child.value HAVING `level` <= 2 ORDER BY `level`;

那么这很容易。

但现在我想了解一些事情,
请注意,在实际数据库中,所有行都有一个唯一的ID和一个“父”列,其中包含他们的邀请者ID,或者如果没有被邀请则为空

  • 假设我想将David插入到Judith的子级,我该如何做?
  • 假设我想获取Mary Hart的父级和父级的父级(array('Henery', 'Joan', 'Mary Hart')),我该如何做?
  • 假设我想从Joan中删除William Hart,我该怎么做?

你尝试过什么?我很不想问,但嵌套集合很复杂,而且你的问题听起来像是在众包你的工作。此外,你甚至还没有开始浏览问题的难度。例如:“如果我想反转大卫和朱迪思,使朱迪思现在取代大卫成为孩子,反之亦然,我该如何做到这一点[不用两次重新索引树]?” - Denis de Bernardy
3个回答

1

要进行更新/删除,您需要增加/减少分支的所有元素的left/right值。
你可以在这里找到一些查询示例。

它的效率有多高我不清楚。

嵌套集在更新/插入/删除大型树时速度非常慢。但在选择方面非常快。
因此,只能将此模型与静态数据一起使用,该数据大部分时间都不会更改,并且该树不会包含数千个节点(否则任何更新将需要几分钟才能完成)。而路径材料化方式的速度更快。


如果我正在使用用户表进行工作,因此数据将经常被插入,最终可能会有数十万行,但不会经常更新/删除,而几乎每次页面加载时都会读取(树形结构是我们网站的核心部分),那么我应该选择嵌套集吗? - Hailwood
@OZ 实际上不仅是分支元素,而是所有左侧 ID 大于当前元素右侧 ID 的元素。 - malko
@Hailwood,不要使用用户表来做。想在层次结构中管理用户?试试使用材料化路径模型。 - OZ_
@epitaph:你错了。当使用整数而不是浮点数时,所有的数据库都会变得非常慢,这纯粹是因为在最轻微的插入/更新/删除操作中可能会发生大量的更新。 - Denis de Bernardy
@Hailwood,不取决于用户数量。 - OZ_
显示剩余3条评论

1
要获取一个节点的父节点,您需要使用具有 left_id < child.left_id 和 right_id > child.right_id 的节点。如果您只想选择直接的祖先,则从前面的集合中选择具有最高 left_id 的那个。
要删除一个节点,请将其删除,然后将所有左/右ID大于已删除元素的右ID的ID降低两次。如果(leftId> deleted.leftId),则leftId-=2同样适用于rightId。
要插入一个节点,请为其添加空间,将 leftId > parent.rightId 的所有节点增加+2,然后 parent.rightId + = 2,然后插入 leftId = parent.rightId-2 和 rightId = parent.rightId-1 的节点。

0

如果你为每个关系使用深度优先搜索,然后再次使用calculate()函数进行更详细的计算,那么所有问题都可以很容易地解决。


海尔伍德:深度优先搜索。 - Micromega

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接