---
# System prepended metadata

title: PostgreSQL 多筆 update 方式探討

---

# PostgreSQL 多筆 update 方式探討
## 前言
看到FB上 Backend 台灣 (Backend Tw) 中 Sam Wong大大發表這篇
https://www.facebook.com/groups/616369245163622/posts/2540355279431666/

裡面有提到使用 upsert 的方式.
來做些測試與探討.

## 傳統方式
```sql=
-- 建立測試table
create table it1208a (
  id int generated always as identity primary key
, k text 
, v int
);

-- 建立臨時表或是使用 select union all 或是 用 values 直接輸入
-- 再搭配 update from where

with t1(key,val) as (
select 'a', 3
union all
select 'b', 5
union all
select 'c', 7
)
update it1208a
   set v = v + val
  from t1
 where k = key;

select *
  from it1208a;

 id | k | v  
----+---+----
  1 | a | 13
  2 | b | 25
  3 | c | 37
(3 rows)

-- 使用 values 方式的部分
with t1 (key, val) as (
values
('a',3),('b',5),('c',7)
)
select *
  from t1;

  from t1;
 key | val 
-----+-----
 a   |   3
 b   |   5
 c   |   7
(3 rows)
```

傳統方式較為繁瑣,臨時表的方式,就是使用 create temp table, 再insert資料.為節省篇幅,在此省略.

## upsert 方式
在PostgreSQL 發展過程中,在9.5版開始支援此方式.
語法是 INSERT, ON CONFLICT UPDATE 這樣的方式.
但是有一個前提,是必須有 unique.

```sql=
create table it1208 (
  id int generated always as identity primary key
, k text not null unique
, v int
);

insert into it1208(k,v) values
('a', 10), ('b', 20), ('c', 30);

insert into it1208(k,v) values
('a', 3), ('b', 5), ('c', 7)
on conflict (k) 
do update
set v = it1208.v + excluded.v;

select *
  from it1208;
 id | k | v  
----+---+----
  1 | a | 13
  2 | b | 25
  3 | c | 37
(3 rows)
```

此方式的優點是在輸入時,可以一直使用此方式,當沒有 k 時就insert,當有 k 時改做update. 注意到有宣告 k 的 unique.

## 是否有另外的方式?
當不同的情境,例如 k 不是 unique 時,像是我們要對某些類別都增加額度時,或是想建立 function 做這類大量 update 操作.
雖然 upsert 可以很方便的使用 values , 但畢竟是語法的一部分,
需要組 Dynamic SQL.

## PostgreSQL 的黑科技
PostgreSQL 與其他資料庫相比,有特殊的如 array 的資料型態.
而搭配array 有set returning functions , 如 unnest().
在 9.4 版開始在select 中支援 rows from
```sql=
ROWS FROM( function_name ( [ argument [, ...] ] ) [ AS ( column_definition [, ...] ) ] [, ...] )
[ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
```

上面提到的array 是存放單一型態,若要多種型態,可以使用json, jsonb. 
看來我們可以使用這些黑科技,搭配組合.直接來看例子.

```sql=
create table it1208b (
  id int generated always as identity primary key
, k text 
, v int
);

insert into it1208b(k,v) values
('a', 10), ('b', 20), ('c', 30);

select * from it1208b;

 id | k | v  
----+---+----
  1 | a | 10
  2 | b | 20
  3 | c | 30
(3 rows)


update it1208b
   set v = v + y.val
  from (select key, value::int as val
          from rows from 
          (jsonb_each('{"a": 3, "b":5, "c": 7}'::jsonb)
          ) x (key,value) ) y
 where k = y.key;

select * from it1208b;

 id | k | v  
----+---+----
  1 | a | 13
  2 | b | 25
  3 | c | 37
(3 rows)
```

這樣不需要 unique 也能多筆資料 update.
但是注意到此方式就是 update 而已.情境並不完全相同.

### 建立 function 的例子
```sql=
create or replace function f_update_it1208b(injs jsonb)
returns void
language sql as
$code$
  update it1208b
   set v = v + y.val
  from (select key, value::int as val
          from rows from (jsonb_each(injs)
          ) x (key,value) ) y
 where k = y.key;
$code$;

-- 可以傳入 jsonb 
select f_update_it1208b(j)
  from (select '{"a": 1, "c": 3}'::jsonb
        union all
        select '{"b": 2}'::jsonb) x(j);

select * from it1208b;

 id | k | v  
----+---+----
  1 | a | 14
  3 | c | 40
  2 | b | 27
(3 rows)
```

此函數使用的language 是 sql, 不是 pl/pgsql.
不需要做語法組合,只需要外部組好 jsonb 傳入.

## 結語
資料庫的應用方式很靈活,效能與便捷性的提升是透過點點滴滴的研究探討,
在此例子中,可以看到PostgreSQL的版本演進逐漸增加功能,帶給我們廣大使用者許多的強大功能.