主頁(yè) > 知識(shí)庫(kù) > PostgreSQL實(shí)現(xiàn)一個(gè)通用標(biāo)簽系統(tǒng)

PostgreSQL實(shí)現(xiàn)一個(gè)通用標(biāo)簽系統(tǒng)

熱門(mén)標(biāo)簽:重慶自動(dòng)外呼系統(tǒng)定制 打電話(huà)智能電銷(xiāo)機(jī)器人授權(quán) 漯河外呼電話(huà)系統(tǒng) 地圖標(biāo)注和圖片名稱(chēng)的區(qū)別 海豐有多少商家沒(méi)有地圖標(biāo)注 辦公外呼電話(huà)系統(tǒng) 外呼調(diào)研系統(tǒng) 合肥公司外呼系統(tǒng)運(yùn)營(yíng)商 美容工作室地圖標(biāo)注

前言

對(duì)資源打標(biāo)簽在建站過(guò)程中是很常見(jiàn)的需求,有些時(shí)候我們需要給文章打標(biāo)簽,有些時(shí)候我們需要給用戶(hù)打標(biāo)簽。實(shí)現(xiàn)一個(gè)標(biāo)簽系統(tǒng)其實(shí)并不難,其本質(zhì)就是一個(gè)多對(duì)多的關(guān)系-我可以對(duì)同一篇博客打多個(gè)標(biāo)簽,同時(shí)也可以把一個(gè)標(biāo)簽打到不同的博客身上。這篇文章主要通過(guò)分析標(biāo)簽系統(tǒng)的原理,并用PostgreSQL來(lái)實(shí)現(xiàn)一個(gè)能夠?yàn)槎喾N資源打標(biāo)簽的標(biāo)簽系統(tǒng)。

1. 單一資源標(biāo)簽系統(tǒng)

先從單一資源開(kāi)始,所謂單一資源便是,我們只給一種數(shù)據(jù)資源打標(biāo)簽。假設(shè)我們需要給博客文章打標(biāo)簽,那么我們需要構(gòu)建以下幾個(gè)表:

  • 文章表posts,用于存儲(chǔ)文章的基本信息。
  • 標(biāo)簽表tags,用于存儲(chǔ)標(biāo)簽的基本信息。
  • 標(biāo)簽-文章表tags_posts,存儲(chǔ)雙方的id并形成多對(duì)多的關(guān)系。

表設(shè)計(jì)圖大概是

先進(jìn)入數(shù)據(jù)庫(kù)引擎并創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)庫(kù)

postgres=# create database blog;
CREATE DATABASE

postgres=# \c blog;
blog=#

通過(guò)SQL語(yǔ)句創(chuàng)建上面所提到的數(shù)據(jù)表

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
);

每個(gè)表都只是包含了該資源最基礎(chǔ)的字段, 到這一步為止其實(shí)已經(jīng)構(gòu)建好了一個(gè)最簡(jiǎn)單的標(biāo)簽系統(tǒng)了。接下來(lái)則是填充數(shù)據(jù),我的策略是添加兩篇文章,五個(gè)標(biāo)簽,給標(biāo)題為Ruby的文章打上language標(biāo)簽,給標(biāo)題為Docker的文章打上container的標(biāo)簽,兩篇文章都要打上tech標(biāo)簽

-- 填充文章數(shù)據(jù)
INSERT INTO posts (body, title) VALUES ('Hello Ruby', 'Ruby');
INSERT INTO posts (body, title) VALUES ('Hello Docker', 'Docker');

-- 填充標(biāo)簽數(shù)據(jù)
INSERT INTO tags (name) VALUES ('language');
INSERT INTO tags (name) VALUES ('container');
INSERT INTO tags (name) VALUES ('tech');

-- 為相關(guān)資源打上標(biāo)簽
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'));

然后分別查詢(xún)兩篇文章都被打上了什么標(biāo)簽。

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)

兩篇文章都被打上期望的標(biāo)簽了,相關(guān)的語(yǔ)句有點(diǎn)長(zhǎng),一般生產(chǎn)線上不會(huì)這樣直接操作數(shù)據(jù)庫(kù)。各種編程語(yǔ)言的社區(qū)一般都對(duì)這種數(shù)據(jù)庫(kù)操作進(jìn)行了封裝,這為編寫(xiě)業(yè)務(wù)代碼帶來(lái)了不少的便利性。

2. 為多種資源打標(biāo)簽

如果只需要對(duì)一個(gè)數(shù)據(jù)表打標(biāo)簽的話(huà),依照上面的邏輯來(lái)設(shè)計(jì)表已經(jīng)足夠了。但是現(xiàn)實(shí)世界往往沒(méi)那么簡(jiǎn)單,假設(shè)除了要給博客文章打標(biāo)簽之外,還需要給用戶(hù)表打標(biāo)簽?zāi)??我們需要把表設(shè)計(jì)得更靈活一些。如果繼續(xù)用tags表來(lái)存標(biāo)簽數(shù)據(jù),為了給用戶(hù)打標(biāo)簽還得另外建一個(gè)名為tags_users的表來(lái)存儲(chǔ)標(biāo)簽與用戶(hù)數(shù)據(jù)之間的關(guān)系。

但更好的做法應(yīng)該是采用名為多態(tài)的設(shè)計(jì)。創(chuàng)建關(guān)聯(lián)表taggings,這個(gè)關(guān)聯(lián)表除了會(huì)存儲(chǔ)關(guān)聯(lián)的兩個(gè)id之外,還會(huì)存儲(chǔ)被打上標(biāo)簽的資源類(lèi)型,我們根據(jù)類(lèi)型來(lái)區(qū)分被打標(biāo)簽的到底是哪種資源,這會(huì)在每條記錄上多存了類(lèi)型數(shù)據(jù),不過(guò)好處就是可以少建表,所有的標(biāo)簽關(guān)系都通過(guò)一個(gè)表來(lái)存儲(chǔ)。

Ruby比較流行的標(biāo)簽系統(tǒng)ActsAsTaggableOn 就沿用了這個(gè)設(shè)計(jì),不過(guò)它的類(lèi)型字段直接存的是對(duì)應(yīng)資源的類(lèi)名,或許是為了更方便編程吧,數(shù)據(jù)大概如下:

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

先通過(guò)taggable_type獲取類(lèi)名,然后再利用taggable_id的數(shù)據(jù)就能準(zhǔn)確獲取相關(guān)的資源了。

a. 修改原表

表設(shè)計(jì)圖大概如下

這里我不重新建表了,而直接修改原有的表,并進(jìn)行數(shù)據(jù)遷移

  • 增加type字段用于存儲(chǔ)資源類(lèi)型。
  • 把原來(lái)的數(shù)據(jù)表改名為更通用的名字taggings。
  • 把原來(lái)的post_id字段改成更通用的名字taggable_id。
  • 給原有的資源填充數(shù)據(jù),type字段統(tǒng)一填數(shù)據(jù)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';

b. 添加用戶(hù)

在給用戶(hù)打標(biāo)簽之前先創(chuàng)建用戶(hù)表,并填充數(shù)據(jù)

-- 創(chuàng)建簡(jiǎn)單的用戶(hù)表
CREATE TABLE users (
 id    SERIAL,
 username  varchar(80),
 age    integer
);


-- 添加一個(gè)名為lan的用戶(hù),并添加兩個(gè)相關(guān)的標(biāo)簽

INSERT INTO users (username, age) values ('lan', 26);

INSERT INTO tags (name) VALUES ('student');
INSERT INTO tags (name) VALUES ('programmer');

c. 給用戶(hù)打標(biāo)簽

接下來(lái)需要給用戶(hù)lan打上標(biāo)簽,對(duì)原有的SQL語(yǔ)句做一些調(diào)整,并在打標(biāo)簽的時(shí)候把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語(yǔ)句為用戶(hù)打上了student以及programmer兩個(gè)標(biāo)簽。

d. 查看標(biāo)簽情況

為了完成這個(gè)任務(wù)我們依然要聯(lián)合三張表進(jìn)行查詢(xún),同時(shí)還要約束type的類(lèi)型

用戶(hù)名為lan的用戶(hù)被打上的所有標(biāo)簽

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)

標(biāo)題為Ruby的文章被打上的所有標(biāo)簽

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,都跟預(yù)期一樣,現(xiàn)在的標(biāo)簽系統(tǒng)就比較通用了。

總結(jié)

本文通過(guò)PostgreSQL的基礎(chǔ)語(yǔ)句來(lái)構(gòu)建了一個(gè)標(biāo)簽系統(tǒng)。實(shí)現(xiàn)了一個(gè)標(biāo)簽系統(tǒng)其實(shí)并不難,各個(gè)語(yǔ)言的社區(qū)應(yīng)該都有相關(guān)的集成。本人也就是想拋開(kāi)編程語(yǔ)言,從數(shù)據(jù)庫(kù)層面來(lái)剖析一個(gè)標(biāo)簽系統(tǒng)的基本原理。

PS: 另外推薦一個(gè)比較好用的Model Design工具dbdiagram,可以用文本的方式對(duì)數(shù)據(jù)表進(jìn)行設(shè)計(jì),邊設(shè)計(jì)邊預(yù)覽。最后還能以PNG,PDF甚至SQL源文件的形式導(dǎo)出。本文的數(shù)據(jù)表配圖均由用該軟件制作。

好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

您可能感興趣的文章:
  • Windows下PostgreSQL安裝圖解
  • PostgreSQL 安裝和簡(jiǎn)單使用
  • PostgreSQL 創(chuàng)建表分區(qū)
  • Postgresql ALTER語(yǔ)句常用操作小結(jié)
  • PostgreSQL新手入門(mén)教程

標(biāo)簽:烏海 來(lái)賓 錦州 株洲 蚌埠 衡陽(yáng) 珠海 晉城

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《PostgreSQL實(shí)現(xiàn)一個(gè)通用標(biāo)簽系統(tǒng)》,本文關(guān)鍵詞  PostgreSQL,實(shí)現(xiàn),一個(gè),通用,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《PostgreSQL實(shí)現(xiàn)一個(gè)通用標(biāo)簽系統(tǒng)》相關(guān)的同類(lèi)信息!
  • 本頁(yè)收集關(guān)于PostgreSQL實(shí)現(xiàn)一個(gè)通用標(biāo)簽系統(tǒng)的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章