# PostgreSQL Upsert 的探討
之前有寫一篇Postgresql 的冪等操作探討[按我前往](https://hackmd.io/@pgsql-tw/Hks3X2EOJe),裡面有使用了upsert.
現在對upsert再來做一些探討.
## 第一個例子
```sql=
insert into upsert0223a
select n, random(1, 10)
  from generate_series(1, 100) g(n);
select last_autoanalyze
     , n_tup_ins
     , n_live_tup
     , n_dead_tup
  from pg_stat_all_tables
 where relname = 'upsert0223a';
       last_autoanalyze       | n_tup_ins | n_live_tup | n_dead_tup
------------------------------+-----------+------------+------------
 2025-02-23 21:30:56.93961+08 |       100 |        100 |          0
(1 row)
insert into upsert0223a values
(50, 3), (88, 4);
ERROR:  23505: duplicate key value violates unique constraint "upsert0223a_pkey"
DETAIL:  Key (id)=(50) already exists.
select n_tup_ins
     , n_live_tup
     , n_dead_tup
  from pg_stat_all_tables
 where relname = 'upsert0223a';
 
 n_tup_ins | n_live_tup | n_dead_tup
-----------+------------+------------
       101 |        100 |          1
(1 row)
```
可以觀察到 第一次輸入100筆,在系統統計表中, insert 了 100 tuple, live tuple 也是100, dead tuple 0.
第二次insert , 使用 values 兩筆, id 分別是 50, 88.
產生了 error, 違反了 PK. 
查詢系統表, 做了101次 insert, 一個 dead tuple.
就是id=50這筆 會先insert,然後違反PK,拋出錯誤,然後id=80這個緊接著的insert 並未操作.
因為 values 多筆,等於是 begin end 包起來的多筆單一insert.當有錯誤發生時,後面的就不執行了.
## 使用upsert 的情況
```sql=
insert into upsert0223a values
(50, 3), (88, 4)
on conflict
do nothing;
INSERT 0 0
select n_tup_ins
     , n_live_tup
     , n_dead_tup
  from pg_stat_all_tables
 where relname = 'upsert0223a';
 n_tup_ins | n_live_tup | n_dead_tup
-----------+------------+------------
       101 |        100 |          1
(1 row)
```
可以觀察到,並未產生error, 而是回報 insert 0 0.
查詢系統表,insert數未增加,dead tuple 也未增加.
可以說upsert是比較聰明的先判斷是否違反限制,而不是先insert,既避免了錯誤發生,也有利於空間的使用,減少了後續做 dead tuple的清理.
錯誤若有發生,我們需要做相對應的處理,捕捉exception!
資料insert了,需要做IO操作.變成dead tuple,空間也佔了,後續清理也是成本,資料頁面又產生了空洞.
upsert雖然看來只是insert 的一點改進,但是實際上帶來的好處是很多的.
## 不是PK的情況
```sql=
create table upsert0223b (
  id int generated always as identity primary key
, gal text unique
);
insert into upsert0223b(gal) values
('佐山愛'),('小島南'),('夢実かなえ');
select n_tup_ins
     , n_live_tup
     , n_dead_tup
  from pg_stat_all_tables
 where relname = 'upsert0223b';
 n_tup_ins | n_live_tup | n_dead_tup
-----------+------------+------------
         3 |          3 |          0
(1 row)
insert into upsert0223b(gal) values
('小島南')
on conflict (gal)
do nothing;
INSERT 0 0
select * from  upsert0223b;
 id |    gal
----+------------
  1 | 佐山愛
  2 | 小島南
  3 | 夢実かなえ
(3 rows)
select n_tup_ins
     , n_live_tup
     , n_dead_tup
  from pg_stat_all_tables
 where relname = 'upsert0223b';
 n_tup_ins | n_live_tup | n_dead_tup
-----------+------------+------------
         3 |          3 |          0
(1 row)
```
可以觀察到,使用unique key, 一樣也能使用upsert!
## 結語
Upsert表面上看來只是語法上,較為便利.實際上卻能帶來開發上與效能及空間利用的改善.
## 感謝
感謝佐山愛,小島南,夢実かなえ.