接下來要介紹的是 UNIQUE Index,當我們希望一個欄位的值,每個值都是獨一無二的,就可以使用 UNIQUE Index。它可以用來確保這個欄位不會有兩個一樣的值,比如說每位 user 都要有獨立不重複的 email,就很適合使用。
在建立 table 時如果有指定 Primary Key,其實 PostgresSQL 也有自動幫他建立 UNIQUE Index,來確保不會有重複的 Primary Key 出現。
PostgreSQL automatically creates a unique index when a unique constraint or primary key is defined for a table.
這邊我們可以檢查 quizzes
table,之前在建立時我們有將 id
設為 Primary Key,可以用指令把 table 所有的 Index 列出來:
SELECT
indexname,
indexdef
FROM
pg_indexes
WHERE
tablename = 'quizzes';
可以看到除了我們自己建立的 idx_quizzes_subject
之外,還有 quizzes_pkey
已經自動建立好 UNIQUE Index 。
你可能會想問,如果欄位是 NULL 的話,這些 NULL 會被當成相同還是不同的呢?依照官方文件的敘述,在預設中 NULL 是會被當成不同的值。
By default, null values in a unique column are not considered equal, allowing multiple nulls in the column. The NULLS NOT DISTINCT option modifies this and causes the index to treat nulls as equal.
但這也是可以調整的,如果希望 NULL 被當成相同值,代表那個欄位只能有一個 row 有 NULL 值的話,也可以設定 NULL NOT DISTINCT
。
除了一開始說的驗證 user email 不可重複外,還有在另外一個情境也很適合用到 UNIQUE Index。在系統設計時,假如有遇到類似的邏輯限制,比如說在 Google Map 底下的留言,每個使用者只能針對一個店家評論一次(就算你再討厭或喜歡一家店也只能留一次~),你會想要怎麼設計呢?
其中有一個方式是在 server 層驗證,去查詢資料庫內這個使用者有沒有針對這個店家評論過,如果有的話就會把請求擋掉。這個做法是行得通的,但如果今天這個寫評論的邏輯在其他地方也會用到,另外一個工程師可能不知道這個邏輯,忘記加了這個驗證,可能會讓使用者對一個店家寫了好多評論。
一個可以避免這件事情發生的方式,就是將 user_id
& store_id
兩個欄位一起設為 UNIQUE Index,這樣就算有另外一組團隊同時在開發,就算真的不小心忘了寫驗證,也不會讓一個使用者對一個店家留多個評論。
以剛剛提到的例子,我們可以這樣做:
reviews
tableCREATE TABLE reviews (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
store_id INTEGER NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
user_id
& store_id
設為 UNIQUE IndexCREATE UNIQUE INDEX unique_user_store_review
ON reviews (user_id, store_id);
INSERT INTO reviews (user_id, store_id, content)
VALUES (1, 100, '這家店很不錯...');
INSERT INTO reviews (user_id, store_id, content)
VALUES (1, 100, '這家店真的太讚了...讚到我想評論第二次...');
就會得到錯誤訊息:
透過以上這個舉例,就可以看到藉由多個欄位組成的 UNIQUE 限制,來達成資料唯一性,把約束同時交給資料庫維護。
NULLS NOT DISTINCT
來修改行為。https://www.postgresql.org/docs/current/indexes-expressional.html