# PostgreSQL 巧用 array 處理 tags 在許多系統中,會使用tag,或是屬性. 我們將介紹傳統的方式,以及PostgreSQL 的 array 兩種方式. ## 傳統方式 會建立三個table,一個用來放標的,一個用來放tag,一個用來放中介. ```sql= create table videos ( vid_id int not null generated always as identity primary key , vid_number text not null ); create table tags ( tag_id int not null generated always as identity primary key , tag_name text not null , unique(tag_name) ); create table video_tags ( vid_id int not null , tag_id int not null , unique(vid_id, tag_id) ); --測試資料 insert into videos(vid_number) values ('MEYD-844'), ('WAAA-293'); insert into tags (tag_name) values ('熟女'),('人妻'),('巨乳'),('泳裝'), ('單體作品'),('高清'),('獨家'),('4K'), ('女教師'),('痴女'),('大屁股'),('中出'); insert into video_tags values (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (2, 9), (2, 10),(2, 11), (2, 12), (2, 3), (2, 1), (2, 8), (2, 7); ``` ### 查詢的方式 #### 一個 tag 的情況 ```sql= select v.vid_number from videos v join video_tags vt on (v.vid_id = vt.vid_id) join tags t on (vt.tag_id = t.tag_id) where t.tag_name = '女教師'; vid_number ------------ WAAA-293 ``` #### 兩個 tag 的情況 ```sql= select v.vid_number from videos v join video_tags vt1 on (v.vid_id = vt1.vid_id) join video_tags vt2 on (vt1.vid_id = vt2.vid_id) join tags t1 on (vt1.tag_id = t1.tag_id) join tags t2 on (vt2.tag_id = t2.tag_id) where t1.tag_name = '熟女' and t2.tag_name = '巨乳'; vid_number ------------ MEYD-844 WAAA-293 (2 rows) ``` #### 三個 tag 的情況 ```sql= select v.vid_number from videos v join video_tags vt1 on (v.vid_id = vt1.vid_id) join video_tags vt2 on (vt1.vid_id = vt2.vid_id) join video_tags vt3 on (vt2.vid_id = vt3.vid_id) join tags t1 on (vt1.tag_id = t1.tag_id) join tags t2 on (vt2.tag_id = t2.tag_id) join tags t3 on (vt3.tag_id = t3.tag_id) where t1.tag_name = '熟女' and t2.tag_name = '巨乳' and t3.tag_name = '泳裝'; vid_number ------------ MEYD-844 ``` 由上面的例子可以看到,查詢方式較為繁瑣. ## 使用array的方式 這時只需要兩個 table,tags 還是一樣,將中介的 table video_tags 合併進 videos 產生新的 videos_array ```sql= create table videos_array ( vid_id int not null primary key , vid_number text not null , vid_tags int[] ); insert into videos_array select v.vid_id, vid_number, array_agg(tag_id) from videos v join video_tags using (vid_id) group by v.vid_id, vid_number; select * from videos_array; vid_id | vid_number | vid_tags --------+------------+---------------------- 1 | MEYD-844 | {1,2,3,4,5,6,7,8} 2 | WAAA-293 | {1,3,7,8,9,10,11,12} (2 rows) --可以建立GIN index create index on videos_array using gin (vid_tags); ``` ### 查詢的方式 ```sql= ---三個 tag 的情況 with t1 as MATERIALIZED ( select array_agg(tag_id) as tag_ids from tags where tag_name in ('熟女', '巨乳', '泳裝') ) select va.vid_number from videos_array va join t1 on va.vid_tags @> t1.tag_ids; vid_number ------------ MEYD-844 ``` 當我們需要查詢具有多個tag,只要善用array 的包含運算子 @> 以及 array_agg() 函數,透過 MATERIALIZED 型態 CTE(只計算一次), 即可方便撰寫SQL,執行速度又快,因為資料筆數精簡又不必透過中介table 多次join. ### 查詢單一 video 的 tag_name 可以使用 unnest() 展開 array ```sql= select t.tag_name from videos_array va cross join unnest(va.vid_tags) as tag_id join tags t using (tag_id) where va.vid_number = 'MEYD-844'; tag_name ---------- 熟女 人妻 巨乳 泳裝 單體作品 高清 獨家 4K (8 rows) ``` ## 結語 PostgreSQL的Array,是一個成熟並具有彈性,可以讓我們精簡查詢,減少空間,有效的反正規化,提高查詢速度的好東西,可以多加利用. ## 致謝 感謝佐山愛小姐.