对资源打标签在建站过程当中是很常见的需求,有些时候咱们须要给文章打标签,有些时候咱们须要给用户打标签。实现一个标签系统其实并不难,其本质就是一个多对多的关系-我能够对同一篇博客打多个标签,同时也能够把一个标签打到不一样的博客身上。这篇文章主要经过分析标签系统的原理,并用PostgreSQL来实现一个可以为多种资源打标签的标签系统。git
先从单一资源开始,所谓单一资源即是,咱们只给一种数据资源打标签。假设咱们须要给博客文章打标签,那么咱们须要构建如下几个表:github
posts
,用于存储文章的基本信息。tags
,用于存储标签的基本信息。tags_posts
,存储双方的id并造成多对多的关系。表设计图大概是sql
先进入数据库引擎并建立对应的数据库数据库
postgres=# create database blog;
CREATE DATABASE
postgres=# \c blog;
blog=#
复制代码
经过SQL语句建立上面所提到的数据表编程
CREATE TABLE posts (
id SERIAL,
body text,
title varchar(80)
);
CREATE TABLE tags (
id SERIAL,
name varchar(80)
);
CREATE TABLE tags_posts (
id SERIAL,
tag_id integer,
post_id integer
);
复制代码
每一个表都只是包含了该资源最基础的字段, 到这一步为止其实已经构建好了一个最简单的标签系统了。接下来则是填充数据,个人策略是添加两篇文章,五个标签,给标题为Ruby
的文章打上language
标签,给标题为Docker
的文章打上container
的标签,两篇文章都要打上tech
标签编程语言
-- 填充文章数据
INSERT INTO posts (body, title) VALUES ('Hello Ruby', 'Ruby');
INSERT INTO posts (body, title) VALUES ('Hello Docker', 'Docker');
-- 填充标签数据
INSERT INTO tags (name) VALUES ('language');
INSERT INTO tags (name) VALUES ('container');
INSERT INTO tags (name) VALUES ('tech');
-- 为相关资源打上标签
INSERT INTO tags_posts (tag_id, post_id) VALUES ((SELECT id FROM tags WHERE name = 'container'), (SELECT id FROM posts WHERE title = 'Docker'));
INSERT INTO tags_posts (tag_id, post_id) VALUES ((SELECT id FROM tags WHERE name = 'tech'), (SELECT id FROM posts WHERE title = 'Docker'));
INSERT INTO tags_posts (tag_id, post_id) VALUES ((SELECT id FROM tags WHERE name = 'tech'), (SELECT id FROM posts WHERE title = 'Ruby'));
INSERT INTO tags_posts (tag_id, post_id) VALUES ((SELECT id FROM tags WHERE name = 'language'), (SELECT id FROM posts WHERE title = 'Ruby'));
复制代码
而后分别查询两篇文章都被打上了什么标签。工具
blog=# SELECT tags.name FROM tags, posts, tags_posts WHERE tags.id = tags_posts.tag_id AND posts.id = tags_posts.post_id AND posts.title = 'Ruby';
name
----------
language
tech
(2 rows)
blog=# SELECT tags.name FROM tags, posts, tags_posts WHERE tags.id = tags_posts.tag_id AND posts.id = tags_posts.post_id AND posts.title = 'Docker';
name
-----------
container
tech
(2 rows)
复制代码
两篇文章都被打上指望的标签了,相关的语句有点长,通常生产线上不会这样直接操做数据库。各类编程语言的社区通常都对这种数据库操做进行了封装,这为编写业务代码带来了很多的便利性。post
若是只须要对一个数据表打标签的话,依照上面的逻辑来设计表已经足够了。可是现实世界每每没那么简单,假设除了要给博客文章打标签以外,还须要给用户表打标签呢?咱们须要把表设计得更灵活一些。若是继续用tags
表来存标签数据,为了给用户打标签还得另外建一个名为tags_users
的表来存储标签与用户数据之间的关系。ui
但更好的作法应该是采用名为多态
的设计。建立关联表taggings
,这个关联表除了会存储关联的两个id以外,还会存储被打上标签的资源类型,咱们根据类型来区分被打标签的究竟是哪一种资源,这会在每条记录上多存了类型数据,不过好处就是能够少建表,全部的标签关系都经过一个表来存储。spa
Ruby比较流行的标签系统ActsAsTaggableOn 就沿用了这个设计,不过它的类型字段直接存的是对应资源的类名,或许是为了更方便编程吧,数据大概以下:
naive_development=# select id, tag_id, taggable_type, taggable_id from taggings;
id | tag_id | taggable_type | taggable_id
----+--------+----------------------+-------------
1 | 1 | Refinery::Blog::Post | 1
2 | 2 | Refinery::Blog::Post | 1
3 | 3 | Refinery::Blog::Post | 1
复制代码
先经过taggable_type
获取类名,而后再利用taggable_id
的数据就能准确获取相关的资源了。
表设计图大概以下
这里我不从新建表了,而直接修改原有的表,并进行数据迁移
type
字段用于存储资源类型。taggings
。post_id
字段改为更通用的名字taggable_id
。type
字段统一填数据post
。ALTER TABLE tags_posts ADD COLUMN type varchar(80);
ALTER TABLE tags_posts RENAME TO taggings;
ALTER TABLE taggings RENAME COLUMN post_id TO taggable_id;
UPDATE taggings SET type='post';
复制代码
在给用户打标签以前先建立用户表,并填充数据
-- 建立简单的用户表
CREATE TABLE users (
id SERIAL,
username varchar(80),
age integer
);
-- 添加一个名为lan的用户,并添加两个相关的标签
INSERT INTO users (username, age) values ('lan', 26);
INSERT INTO tags (name) VALUES ('student');
INSERT INTO tags (name) VALUES ('programmer');
复制代码
接下来须要给用户lan
打上标签,对原有的SQL语句作一些调整,并在打标签的时候把type
字段填充为user
。
INSERT INTO taggings (tag_id, taggable_id, type) VALUES ((SELECT id FROM tags WHERE name = 'student'), (SELECT id FROM users WHERE username = 'lan'), 'user');
INSERT INTO taggings (tag_id, taggable_id, type) VALUES ((SELECT id FROM tags WHERE name = 'programmer'), (SELECT id FROM users WHERE username = 'lan'), 'user');
复制代码
上述的SQL语句为用户打上了student
以及programmer
两个标签。
为了完成这个任务咱们依然要联合三张表进行查询,同时还要约束type
的类型
lan
的用户被打上的全部标签blog=# SELECT tags.name FROM tags, users, taggings WHERE tags.id = taggings.tag_id AND users.id = taggings.taggable_id AND taggings.type = 'user' AND users.username = 'lan';
name
------------
student
programmer
(2 rows)
复制代码
Ruby
的文章被打上的全部标签blog=# SELECT tags.name FROM tags, posts, taggings WHERE tags.id = taggings.tag_id AND posts.id = taggings.taggable_id AND taggings.type = 'post' AND posts.title = 'Ruby';
name
----------
language
tech
复制代码
OK,都跟预期同样,如今的标签系统就比较通用了。
本文经过PostgreSQL的基础语句来构建了一个标签系统。实现了一个标签系统其实并不难,各个语言的社区应该都有相关的集成。本人也就是想抛开编程语言,从数据库层面来剖析一个标签系统的基本原理。
PS: 另外推荐一个比较好用的Model Design工具dbdiagram,能够用文本的方式对数据表进行设计,边设计边预览。最后还能以PNG,PDF甚至SQL源文件的形式导出。本文的数据表配图均由用该软件制做。