Djangoを利用してupsert
のような挙動を実現するためには、update_or_create()
を利用すれば良いようだった。
upsertとは
SQLにおいて、既にデータが登録されていたらUPDATEを行い、データがなければINSERTを行うという処理の事を指している。軽く調べたところ、INSERT
や UPDATE
のように UPSERT
というコマンドがあるわけではなく、例えばPostgreSQLではMERGE
コマンドを利用して同様の動作を実現しているようだった。
update_or_create()について
公式リファレンス のupdate_or_create()によると、例えばtry~except model名.DoesNotExist
を利用して、save
or update
を呼び出すようなコードをupdate_or_create()
を利用すれば記述できると書かれてある。
実際に書いてみた一例
以下のリポジトリのコードを利用して記述。 https://github.com/nibuno/djangoTaskApp
Djangoのバージョンは4.2.9で試した。
from datetime import date user = User.objects.get(username="nibutan") # 1回目のINSERT処理 task, created = Task.objects.update_or_create( title="ログを出力する", created_user=user, defaults={ "content": "どんな操作がされたのかログに残して分かるようにする。", "status": 1, "order": 10, "limit_date": date(2024, 6, 1), "updated_user": user } ) # 2回目のUPDATE処理 task, created = Task.objects.update_or_create( title="ログを出力する", created_user=user, defaults={ "content": "どんな操作がされたのかログに残して分かるようにする。django-structlogを使ってみるのも良さそう。", "status": 1, "order": 10, "limit_date": date(2024, 6, 1), "updated_user": user } )
上記のように書くことで、いわゆるupsert
の処理を実現することができた。
また、例によって発行されるSQLを確認したところ、以下のようにシンプルなUPDATE文の発行(UPSERT処理を書いているわけではない)だった。これは、DBによってupsert
を実現するための構文が異なるのでDjango側で吸収しているのだと想像している。
# 初回のINSERT時 (0.000) BEGIN; args=None; alias=default (0.001) SELECT "task"."id", "task"."title", "task"."content", "task"."status", "task"."order", "task"."created_user_id", "task"."limit_date", "task"."updated_user_id", "task"."created_at", "task"."updated_at" FROM "task" WHERE ("task"."created_user_id" = 1 AND "task"."title" = 'ログを出力する' 21; args=(1, 'ログを出力する'); alias=default (0.000) SAVEPOINT "s281473828298784_x2"; args=None; alias=default (0.009) INSERT INTO "task" ("title", "content", "status", "order", "created_user_id", "limit_date", "updated_user_id", "created_at", "updated_at") VALUES ('ログを出力する', 'どんな操作がされたのかログに残して分かるようにする', 1, 10, 1, '2024-06-01', 1, '2024-06-01 07:04:03.835112', '2024-06-0ask"."id"; args=('ログを出力する', 'どんな操作がされたのかログに残して分かるようにする', 1, 10, 1, '2024-06-01', 1, '2024-06-01 07:04:03.835112', 'ias=default (0.000) RELEASE SAVEPOINT "s281473828298784_x2"; args=None; alias=default (0.005) COMMIT; args=None; alias=default # 2回目のUPDATE時 (0.000) BEGIN; args=None; alias=default (0.001) SELECT "task"."id", "task"."title", "task"."content", "task"."status", "task"."order", "task"."created_user_id", "task"."limit_date", "task"."updated_user_id", "task"."created_at", "task"."updated_at" FROM "task" WHERE ("task"."created_user_id" = 1 AND "task"."title" = 'ログを出力する' 21; args=(1, 'ログを出力する'); alias=default (0.002) UPDATE "task" SET "content" = 'どんな操作がされたのかログに残して分かるようにする。django-structlogを使ってみるのも良さそう。', "status" = 6-01', "updated_user_id" = 1, "created_at" = '2024-06-01 07:04:03.835112', "updated_at" = '2024-06-01 07:05:18.849078' WHERE "task"."id" = 42; args=('どんな操作がされたのかログに残して分かるようにする。django-structlogを使ってみるのも良さそう。', 1, 10, '2024-06-01', 1, '2024-06-01 07:04:03.83); alias=default (0.003) COMMIT; args=None; alias=default