关于用户多标签的更新

在项目开发中,经常要现实这样的一个功能——关系表更新。比如,一个用户可以设置多个标签,而一个标签下又可以有多个用户,两者是多对多的关系,用一张关系表记录用户的标签数据,那么,当更新用户标签的时候,怎样安排执行流程,才是最优的呢?

数据关系

假设数据库中有三张表,user,tag,user_tag:

假设数据库中有三张表,

user:

id name
1 user1
2 user2
3 user3

tag:

id title
1 tag1
2 tag2
3 tag3

user_tag:

| id | user_id | tag_id | deleted_at|
| — | — | — | — | — |
| 1 | 1 | 1 | null |
| 2 | 2 | 1 | null |
| 3 | 3 | 1 | null |
| … | … | … | null |

user_tag表记录了user表和tag表之间的关系,并且user_tag表内使用软删除。

问题描述

如果用户user1的标签为tag1、tag2和tag3,现在想更新为tag2、tag4和tag6,那么应该怎样操做?

解决方案

最暴力的解决办法就是delete旧数据,然后insert新数据。但是项目中,为了安全,一般会禁用硬删除,而使用软删除。如果用软删除结合insert,关系表就会越来越大。

比较常用的解决方案是:实时更新和差集更新。

实时更新

当给用户添加一个标签时,后台立刻更新;当给用户移除一个标签时,后台也立刻更新,这样的做法,
实现起来很简单,(伪)代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// with laravel
// in UserTag Model

static public function addUserTag($user_id, $tag_id) {
$user_tag = self::where(['user_id'=> $user_id, 'tag_id'=> $tag_id])->first();
if($user_tag) {
$user_tag->deleted_at = null;
return $user_tag->save();
} else {
$user_tag = new self;
$user_tag->user_id = $user_id;
$user_tag->tag_id = $tag_id;
$user->save();
}
}

static public function removeUserTag($user_id, $tag_id) {
return self::where(['user_id'=> $user_id, 'tag_id'=> $tag_id])->update(['deleted_at'=> time()]);
}

差集更新

如果使用实时更新,那么每次操作都要向后台发送请求,这样的请求一般都是小而多,倒不如使用批量更新。比较一下新数据和就数据,针对差集进行更新,(伪)代码如下:

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

// with laravel
// in UserTag Model

static public function getAllUserTagID($user_id) {
$result = self::(['user_id'=> $user_id])->select('tag_id');
if($result) {
return $result->pluck('tag_id')->toArray();
}
return [];
}

static public function insertUserTags($user_id, array $tag_ids) {
$data = [];
foreach ($tag_ids as $tag_id) {
$data[] = [
'user_id' => $user_id,
'tag_id' => $tag_id,
];
}
return self::insert($data);
}

static public function updateUserTags($user_id, array $tag_ids, $action = 'in') {
self::where(['user_id'=> $user_id])->whereIn('tag_id', $tag_ids);
if ($action == 'in') {
return $builder->update(['deleted_at'=> null]);
} else if ($action == 'out') {
return $builder->update(['deleted_at'=> time()]);
}
return false;
}

// in UserTag Controller
public function postUserTags($request) {
$user_id = session('user_id');
$old_tag_ids = UserTag::getAllUserTagID($user_id);
$new_tag_ids = $request->input('tag_ids'); // is array

$save_tag_ids = array_intersect($old_tag_ids, $new_tag_ids); // 需要保留的标签id
$throw_tag_ids = array_diff($old_tag_ids, $save_tag_ids); // 需要删除的标签id
$add_tag_ids = array_diff($new_tag_ids, $save_tag_ids); // 需要添加的标签id

UserTag::updateUserTags($employee->id, $save_tag_ids, 'in');
UserTag::updateUserTags($employee->id, $throw_tag_ids, 'out');
UserTag::insertUserTags($employee->id, $add_tag_ids);

}

最后

最后说两句,多点关注array_intersectarray_diff等等PHP数组函数的使用。