PostgreSQL 如何實現一個只有一行數據的表

大家好,我是隻談技術不剪髮的 Tony 老師。今天我們來討論一個有趣的話題:如何在 PostgreSQL 中實現一個只能存儲一行數據的表。

假如我們有一個表 t_version,用於記錄應用系統的版本信息:

create table t_version(version text not null, update_at timestamp not null);

第一次安裝應用程序時需要生成一條記錄,以後升級系統時需要更新版本信息,但不允許用戶刪除該記錄。這種需求該如何實現?

基於常量表達式創建一個唯一索引

在 PostgreSQL 中想要限制表中只能包含一行數據實際上非常簡單,就是利用表達式索引(也叫函數索引)基於常量值創建一個唯一索引。

針對上面的問題,我們可以爲表 t_version 創建一個唯一索引:

create unique index t_version_uk on t_version ( (1) );

索引 t_version_uk 是一個基於常量表達式 (1) 的函數索引,並且具有唯一性。也就是說,表中任何數據行對應的索引值都是 1,而唯一索引只允許一個 1,因此該表中最多隻能存儲一行數據。😎

使用 INSERT ON CONFLICT 插入和更新

第一次插入數據時可以使用 INSERT 語句,但是如果已經存在數據時就會返回錯誤:

-- 初始化安裝
insert into t_version values ('系統版本 1.0.0', current_timestamp);

-- 升級軟件版本
insert into t_version values ('系統版本 1.1.0', current_timestamp);
ERROR:  duplicate key value violates unique constraint "t_version_uk"
DETAIL:  Key ((1))=(1) already exists.

第二次插入數據時返回了唯一約束衝突。所以,如果系統進行了升級,就需要使用 UPDATE 語句更新版本信息:

UPDATE t_version
SET version = '系統版本 1.1.0',
    update_at = current_timestamp;

但是問題在於我們需要判斷表中是否已經存在數據,然後執行不同的語句。爲了解決這個問題,可以使用 INSERT ON CONFILCT 語句,也稱爲 UPSERT 語句:

-- 清除數據
truncate table t_version;

-- 初始化安裝
insert into t_version values ('系統版本 1.0.0', current_timestamp);
on conflict ((1)) 
do update set version = excluded.version, 
              update_at = excluded.update_at;
 
select * from t_version;
version      |update_at          |
-------------|-------------------|
系統版本 1.0.0|2020-07-02 21:57:06|

-- 升級軟件版本
insert into t_version values ('系統版本 1.1.0', current_timestamp)
on conflict ((1)) 
do update set version = excluded.version, 
              update_at = excluded.update_at;

select * from t_version;
version      |update_at          |
-------------|-------------------|
系統版本 1.1.0|2020-07-02 21:58:55|

通過使用 ON CONFLICT 選項,可以使用相同的 INSERT 語句實現數據插入和更新。

通過觸發器禁止數據刪除

最後一個問題就是需要避免版本信息被誤刪除,這個可以通過一個觸發器來實現。首先,創建一個觸發器函數:

CREATE OR REPLACE FUNCTION version_del_func()
  RETURNS trigger
AS $$
BEGIN
  RAISE '禁止刪除版本信息!';
END; $$
LANGUAGE plpgsql;

該函數直接返回了一個異常錯誤信息。然後爲 t_version 表創建一個刪除觸發器:

CREATE TRIGGER tri_version_del 
BEFORE DELETE ON t_version
FOR EACH STATEMENT
EXECUTE FUNCTION version_del_func();

觸發器是一個語句級 BEFORE 觸發器,在任何刪除語句之前調用函數 version_del_func 返回錯誤信息。

我們執行以下語句刪除版本信息:

delete
from t_version;
ERROR: 禁止刪除版本信息!
  Where: PL/pgSQL function version_del_func() line 3 at RAISE

select * from t_version;
version      |update_at          |
-------------|-------------------|
系統版本 1.1.0|2020-07-02 21:58:55|

刪除語句返回了錯誤信息,t_version 中的數據仍然存在。不過需要注意的是,TRUNCATE TABLE 語句仍然可以清除表中的數據,因爲它不會觸發 DML 觸發器。

總結

在 PostgreSQL 中索引列不一定是表中的字段,也可以是基於表的一個或多個字段的函數或標量表達式,甚至可以是一個常量表達式。該功能通常用於提高使用表達式作爲過濾條件時的查詢性能,本文演示了它的另一種特殊的用途。

除了本文使用的函數索引,還有沒有其他的實現方法?歡迎關注❤️、評論📝、點贊👍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章