什么是分层数据(Hierarchical Data)?

  • 分层数据是数据项在整个树结构以父子关系相互关联时形成的数据结构。
  • 分层数据的应用场景:组织架构,省份城市,用户推广关系。
  • 在分层数据中,每个 “子” 节点只有一个 “父节点”,但每个父节点可以有多个子节点。位于层次结构顶部的第一个节点称为根节点。当需要检索信息时,将从根节点向下扫描整棵树。由于每次用户进行查询时都需要扫描整棵树,因此系统不灵活、速度慢。现代数据库已经发展到在相同数据上使用多个层次结构,以便更快、更轻松地进行搜索。

嵌套集合(Nested Set)模型

什么是嵌套集?

嵌套集合(Nested Set)模型

  • 嵌套集(Nested Set)模型的算法也叫做预排序遍历树算法 MPTT(Modified Preorder Tree Taversal)。
  • 嵌套集模型是根据树遍历对节点进行编号,遍历每个节点两次,按访问顺序分配编号,两次访问时都分配编号。这为每个节点留下了两个数字,它们存储为两个属性。查询变得便宜:可以通过比较这些数字来测试层次结构成员资格。更新需要重新编号,因此成本很高。
  • 通俗一点解释就是每个集体都是有边界的,我邀请的子用户和我就形成了一个小的团体,这个团体里所有的人都和我有直接或间接的邀请关系。
1
2
3
4
5
6
7
8
9
10
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`_lft` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '左边界',
`_rgt` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '右边界',
`parent_id` int(10) UNSIGNED DEFAULT NULL COMMENT '父级编号',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB CHARACTER SET=utf8mb4 COMMENT '用户信息表';

laravel-nestedset 的用法

  • 安装
1
composer require kalnoy/nestedset
  • Model初始化配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace App\Model;

use Kalnoy\Nestedset\NodeTrait;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
use NodeTrait;

protected $table = 'users';

public $timestamps = false;

public function getLftName()
{
return '_lft';
}

public function getRgtName()
{
return '_rgt';
}

public function getParentIdName()
{
return 'parent_id';
}
}
  • 数据示例

数据示例

  • 新增节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// #1 Using deferred insert
$node->appendToNode($parent)->save();

// #2 Using parent node
$parent->appendNode($node);

// #3 Using parent's children relationship
$parent->children()->create($attributes);

// #5 Using node's parent relationship
$node->parent()->associate($parent)->save();

// #6 Using the parent attribute
$node->parent_id = $parent->id;
$node->save();

// #7 Using static method
User::create($attributes, $parent);
  • 查询是否为根节点

    1
    2
    $o_user = User::find(1);
    $flag = $o_user->isRoot();
  • 查询父级和子级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//ancestorsOf 查询指定用户的所有直属父级,数组返回结果默认为根节点
//ancestorsAndSelf 查询指定用户的所有直属父级和自己,数组返回结果默认为根节点
$result = User::select(['id as user_id', 'parent_id'])
->ancestorsOf(33)
->toArray();
print_r($result);

Array
(
[0] => Array
(
[user_id] => 3
[parent_id] => null
)

[1] => Array
(
[user_id] => 10
[parent_id] => 3
)
)

//descendantsOf 查询指定用户的所有子级用户
//descendantsAndSelf 查询指定用户的所有子级用户包含自己
$result = User::select(['id as user_id', 'parent_id'])
->descendantsOf(3)
->toArray();
print_r($result);

Array
(
[0] => Array
(
[user_id] => 10
[parent_id] => 3
)

[1] => Array
(
[user_id] => 11
[parent_id] => 3
)

[2] => Array
(
[user_id] => 12
[parent_id] => 3
)

[3] => Array
(
[user_id] => 31
[parent_id] => 10
)

[4] => Array
(
[user_id] => 32
[parent_id] => 10
)

[5] => Array
(
[user_id] => 33
[parent_id] => 10
)

)
  • 深度查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//层级深度查询,查询指定层级的所有用户
$result = User::select(['id as user_id', 'parent_id'])
->withDepth()
->having('depth', '=', 1)
->get()
->toArray();
print_r($result);

Array
(
[0] => Array
(
[user_id] => 4
[parent_id] => 1
[depth] => 1
)

[1] => Array
(
[user_id] => 5
[parent_id] => 1
[depth] => 1
)

[2] => Array
(
[user_id] => 6
[parent_id] => 1
[depth] => 1
)

[3] => Array
(
[user_id] => 7
[parent_id] => 2
[depth] => 1
)

[4] => Array
(
[user_id] => 8
[parent_id] => 2
[depth] => 1
)

[5] => Array
(
[user_id] => 9
[parent_id] => 2
[depth] => 1
)

[6] => Array
(
[user_id] => 10
[parent_id] => 3
[depth] => 1
)

[7] => Array
(
[user_id] => 11
[parent_id] => 3
[depth] => 1
)

[8] => Array
(
[user_id] => 12
[parent_id] => 3
[depth] => 1
)

)

  • 修复树状结构。
1
User::fixTree();

总结

  • 实现无限级分类时,邻接表模型增加节点相对容易,但查询需要使用递归,分层数据越多效率低。嵌套集合模型增加和修改节点比较复杂,但查询时比较方便。一般的联动数据我们可以使用邻接表递归来实现,像用户邀请关系这种可以无限分级的还是推荐使用嵌套集合模型。

引用