SQLAlchemyでまとめてupsert
きっかけ
SQLAlchmey(ver 1.3)において、まとめてupsert(bulk upsert) するのはどうすればいいのか学んだので、備忘録で書いとこうと思いました。
その前に・・・・
upsertについて
みなさん、upsertって知ってますか?
そんなの基本だと言われるかもしれませんが、なんとなくSQLしてきた人からすると、
selectとinsertとupdateとdeleteくらいしか、実は知らなかったりします。(私だけかな・・・
upsertとは?
upsertとは、対象のデータがDBになければ追加、あれば更新してくれる命令
update と insert を組み合わせて、upsertと呼ばれています
upsertを使うメリット
upsertは select してレコードがあったら、update命令にして、
レコードがなければ、insert命令にするって感じでアプリ側で操作することで実現することも可能
ただし、selectとupdaete もしくは insert の組み合わせでは、
DBに2回SQLの問い合わせが必ず発生する。
それなら1回の問い合わせで済むupsertを使った方が、DBの負担は少ない。
DBへの負荷はなるだけ、少なく済ませるのが、やはり暗黙の了解でしょうか・・・
参考:https://web-academia.org/482/
本題
公式ドキュメントはこちら
https://docs.sqlalchemy.org/en/14/dialects/postgresql.html#insert-on-conflict-upsert
単発、つまり1レコードについての書き方は以下のサイトがわかりやすかったのでこちらを参考にする
https://qiita.com/nsuhara/items/86570f789093222252b1#upsertの実装
上記のサイトをベースに
複数レコードについては、書き方はこんな感じになる(Python)
from sqlalchemy.dialects import postgresql class FeedbackNew(db.Model): __tablename__ = 't_feedback_new' id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(255), nullable=False) def bulk_upsert(record_lists) -> None: # stmt = ステートメント らしい stmt = postgresql.insert(FeedbackNew).values([ dict( id=x.id, title=x.title, ) for x in record_lists ]) do_stmt = stmt.on_conflict_do_update( index_elements=['id'], # set_ に渡せるのはdict型のみなので注意 # 更新対象を渡す set_=dict( title=stmt.excluded.title, ), ) # sessionの渡してるところとかは割愛します # とりあえず、どこかで session.executeするのを忘れずに・・・・ session.execute(do_stmt)
ちなみに・・・
- これは PostgreSQL ver.9.5以降 の機能らしい。
on_conflict_do_update
は、SQLAlchemyが提供するメソッドon_conflict_do_update
は、PostgreSQL固有のinsert()関数内のON CONFLICT をサポートしている- ステートメントは、1つのSQLの命令を指す、(select文, insert文とか)
https://style.potepan.com/articles/26487.html
細かいことは調べてないが、とりあえず、こんな書き方で一気にupsertすることができるらしい 🧚♂️