3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UPSERT

Posted at

UPSERTって何だっけ?って思ったので、自分用に調べつつPostgreSQLでの使い方をまとめてみました。

UPSERTって

検索してみると、以下のようなイメージらしいです。

データの新規挿入(INSERT)ができれば挿入を行い、新規挿入ができなければ更新(UPDATE)を行うもの。MERGE文と同様。

データの投入時に既存データがあれば更新して最新化、ということで、システム的にはかなり使い勝手がよさそうな機能です。

UPSERT機能は、INSERT/UPDATEのようにUPSERT文というものがあるわけではないですが、INSERT文のオプションや専用のMERGE文を使うことで、実現できるとのことです。

PostgreSQLでお試し

PostgreSQLでは、以下の二つの方法でUPSERTが可能です。

  • INSERT INTO ... ON CONFLICT DO UPDATE SET ...
    (PostgreSQL9.5~)
  • MERGE INTO ... USING ... ON ... WHEN [NOT] MATCHED THEN ...
    (PostgreSQL15~)

INSERTの場合

こちらは昔からある方です。
ON CONFLICTで制約名もしくは制約がある列名を指定して、制約に違反した場合にUPDATEに切り替えることができます。

SQLのイメージ

INSERT INTO テーブル名(列名,...) 挿入するデータ
ON CONFLICT 制約がある列名 or ON CONFLICT 制約名
DO UPDATE SET 更新する列名 = EXCLUDED.更新する列名

普通のINSERT文の後ろに 使用する制約(ON CONFLICT ...)と更新する際のSET文(DO UPDATE SET ...)を記載します。

忘れそうなポイントとして、UPDATEのSET文の右辺には VALUESなどで指定した挿入するデータを書くと思います。この時シンプルなSQLでは左辺と右辺が当然同じ名前であるため、区別するために専用のEXCLUDEDというテーブル名が与えられてます。これを書いてあげないとエラーになるので要注意です。

ということで、実際にINSERT ON CONFLICT文を使ってみました。

まずは、制約が必要なので、シンプルに主キーがあるテーブルを作ります。

test=# CREATE TABLE test_tbl(id int PRIMARY KEY, num int);
CREATE TABLE
test=# 
test=# \d test_tbl
              Table "public.test_tbl"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 num    | integer |           |          | 
Indexes:
    "test_tbl_pkey" PRIMARY KEY, btree (id)

test=# 

主キーがあるid列を指定してデータが被った場合は、他の列(num)を更新するクエリを実行します。

test=# INSERT INTO test_tbl VALUES(1,1),(2,2),(3,3)
test-# ON CONFLICT(id)
test-# DO UPDATE SET num = EXCLUDED.num;
INSERT 0 3
test=# SELECT * FROM test_tbl;
 id | num 
----+-----
  1 |   1
  2 |   2
  3 |   3
(3 rows)

test=# 
test=# 
test=# INSERT INTO test_tbl VALUES(1,10)
test-# ON CONFLICT(id)
test-# DO UPDATE SET num = EXCLUDED.num;
INSERT 0 1
test=# SELECT * FROM test_tbl;
 id | num 
----+-----
  2 |   2
  3 |   3
  1 |  10
(3 rows)

test=# 
test=# INSERT INTO test_tbl VALUES(2,200)
test-# ON CONFLICT ON CONSTRAINT test_tbl_pkey
test-# DO UPDATE SET num = EXCLUDED.num;
INSERT 0 1
test=# SELECT * FROM test_tbl;
 id | num 
----+-----
  3 |   3
  1 |  10
  2 | 200
(3 rows)

test=# 

MERGEの場合

MERGE文は数か月前に出た最新版で実装された機能です。
こちらは、Oracle Databaseと同様なUPSERT文となっているみたいです。
文法が複雑な分、色々と条件分けして処理ができます。

SQLのイメージ

MERGE INTO 挿入先のテーブル名
USING 挿入データがあるテーブル AS 別名 ON 結合条件
WHEN [NOT] MATCHED THEN
実行する処理(INSERT/UPDATE/DELETE)

MERGE文では制約などは使いません。
挿入先のテーブルと挿入したいデータを結合し、
結合できたデータ(重複するデータ)と結合できなかったデータ(重複しないデータ)などの条件ごとにINSERT/UPDATE/DELETEなどの実行するアクションを指定する形式です。

実行するアクションは、テーブルの指定部分を除いた INSERT/UPDATE/DELETE文で記述します。

例)

  INSERT (id,num) VALUES(b.id,b.num)
  UPDATE SET num = b.num
  DELETE

今度は、MERGE文を使って先ほどと全く同じSQLを作成して試してみます。
まず、同じテーブルを作ります(MERGE文の場合は主キーが無くても問題ないです)。

test=# CREATE TABLE test_tbl(id int PRIMARY KEY, num int);
CREATE TABLE
test=# 
test=# \d test_tbl
              Table "public.test_tbl"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 num    | integer |           |          | 
Indexes:
    "test_tbl_pkey" PRIMARY KEY, btree (id)

test=# 

挿入したいデータと一致する行(WHEN MATCHED THEN)は重複してるのでUPDATE文、一致しない行(WHEN NOT MATCHED THEN)は存在しないデータなのでINSERT文を実行するようにします。

  • 使用するSQL
MERGE INTO test_tbl a
USING (VALUES(挿入するデータ)) AS b(id,num) ON a.id = b.id
WHEN MATCHED THEN
  UPDATE SET num = b.num
WHEN NOT MATCHED THEN
  INSERT (id,num) VALUES(b.id,b.num);

実行すると、INSERTのときと同じようにUPSERT処理となっていることが確認できます。

test=# MERGE INTO test_tbl a
test-# USING (VALUES(1,1),(2,2),(3,3)) AS b(id,num) ON a.id = b.id
test-# WHEN MATCHED THEN
test-#   UPDATE SET num = b.num
test-# WHEN NOT MATCHED THEN
test-#   INSERT (id,num) VALUES(b.id,b.num);
MERGE 3
test=# SELECT * FROM test_tbl;
 id | num 
----+-----
  1 |   1
  2 |   2
  3 |   3
(3 rows)

test=# 
test=# 
test=# MERGE INTO test_tbl a
test-# USING (VALUES(1,10)) AS b(id,num) ON a.id = b.id
test-# WHEN MATCHED THEN
test-#   UPDATE SET num = b.num
test-# WHEN NOT MATCHED THEN
test-#   INSERT (id,num) VALUES(b.id,b.num);
MERGE 1
test=# SELECT * FROM test_tbl;
 id | num 
----+-----
  2 |   2
  3 |   3
  1 |  10
(3 rows)

test=# MERGE INTO test_tbl a
test-# USING (VALUES(2,200)) AS b(id,num) ON a.id = b.id
test-# WHEN MATCHED THEN
test-#   UPDATE SET num = b.num
test-# WHEN NOT MATCHED THEN
test-#   INSERT (id,num) VALUES(b.id,b.num);
MERGE 1
test=# SELECT * FROM test_tbl;
 id | num 
----+-----
  3 |   3
  1 |  10
  2 | 200
(3 rows)

test=# 

参考情報

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?