一、需求背景
在數據庫表里,一般都有主鍵,主鍵是不能重復的,因為是唯一標識。假設這個時候需求來了,需要插入一組數據,這些數據中有些是完全新的,可以直接插入(insert),但有些主鍵內容是和原本表內的數據主鍵內容是一致的,這些就無法直接插入了,而是執行更新(update)操作。這時候就比較麻煩了,因為如果全部都是通過insert操作,必然會因為發現有重復唯一主鍵而報錯。
一般來說,這時候需要通過業務代碼來進行判斷:有重復的主鍵值就執行更新操作,沒有就插入操作。但是PostgreSQL就提供了很好的解決方法,語法如下:
-- 1、主鍵id不重復就插入,否則更新
insert into 表名稱 (字段a, 字段b, ...) values (value_a, value_b, ...) on conflict (主鍵id) do update set ...略 -- 2、直接綁定主鍵名稱,主鍵重復則更新
insert into 表名稱 (字段a, 字段b, ...) values (value_a, value_b, ...) on conflict on constraint this_table_key do update set ...略
二、PostgreSQL 的 upsert 簡介
PostgreSQL 的 upsert 功能:當記錄不存在時,執行插入;否則,進行更新。
在關系數據庫中,術語 upsert 被稱為合並(merge),意思是,當執行 INSERT 操作時,如果數據表中不存在對應的記錄,PostgreSQL 執行插入操作;如果數據表中存在對應的記錄,則執行更新操作。這就是為什么將其稱為 upsert(update or insert)的原因。
通過 INSERT ON CONFLICT 來使用 upsert 功能:
INSERT INTO table_name(column_list) VALUES(value_list) ON CONFLICT target action;
1、target 可以是:
- (column_name):一個字段名
- ON CONSTRAINT constraint_name:其中的 constraint_name 可以是一個唯一約束的名字
- WHERE predicate:帶謂語的 WHERE 子句
2、action 可以是:
- DO NOTHING:當記錄存在時,什么都不做
- DO UPDATE SET column_1 = value_1, … WHERE condition:當記錄存在時,更新表中的一些字段
注意,ON CONFLICT 只在 PostgreSQL 9.5 以上可用。
三、PostgreSQL 的 upsert 示例
-- 我們新建一個 customers 表來進行演示:
CREATE TABLE customers ( customer_id serial PRIMARY KEY, name VARCHAR UNIQUE, email VARCHAR NOT NULL, active bool NOT NULL DEFAULT TRUE ); -- customers 表有4個字段:customer_id、name、email 和 active。 -- 其中,name 字段有唯一約束,用於確保客戶的唯一性。
-- 下面,往 customers 表里插入幾行數據:
#SELECT * FROM customers; customer_id | name | email | active -------------+-----------+-----------------------+--------
1 | IBM | contact@ibm.com | t 2 | Microsoft | contact@microsoft.com | t 3 | Intel | contact@intel.com | t (3 rows) ————————————————
假設 Microsoft 更換了聯系方式 email:由 contact@microsoft.com 變成了 hotline@microsoft.com,我們可以使用 UPDATE 語句進行修改。然而,為了演示 upsert 功能,我們使用 INSERT ON CONFLICT 語句
INSERT INTO customers (NAME, email) VALUES ( 'Microsoft', 'hotline@microsoft.com' ) ON CONFLICT ON CONSTRAINT customers_name_key DO NOTHING;
這個語句指明了,當數據存在時,什么都不做(DO NOTING)。下面的語句有一樣的效果,區別在於使用的是 name 字段,而不是約束的名字:
INSERT INTO customers (name, email) VALUES ( 'Microsoft', 'hotline@microsoft.com' ) ON CONFLICT (name) DO NOTHING;
我們的目標是修改客戶的 email,所以應該用這條語句:
INSERT INTO customers (name, email) VALUES ( 'Microsoft', 'hotline@microsoft.com' ) ON CONFLICT (name) DO UPDATE
SET email = EXCLUDED.email; upsert