【PostgreSQL】Select結果でUpdateする3パターン

概要

PostgreSQLを使っているシステムで、Select結果でUpdateしたい機能があったので、当初は相関副問い合わせを使ったSQLで実装していました。ところが、総合テストの段階まで来て、速度パフォーマンスが良くないことがわかり、SQLを書き直した経過を紹介します。

スポンサーリンク

前提となるテーブル

実際のシステム内ではもっと多数の列を持つテーブルなのですが、説明のため簡単にしていますが、おおよそ下記のようなテーブルがあることにします。

user_id(PK) user_name best_point
1 Aさん 80
2 Bさん 90
3 Cさん 75
4 Dさん 95
5 Eさん 70

test_id user_id(FK) point
1 1 85
1 4 90
1 5 80

相関副問い合わせ

今までOracleを使うことが多かったので、Oracleでも使えていた相関副問い合わせを使って実装していました。

update m_users
   set best_point = coalesce((
         select max(point)
           from d_records
          where user_id = m.user_id
            and test_id = 1
            and point >= m.best_point
       ), best_point)

coalesceを使うことで、Select結果がない場合には既存の値で更新するようにしています。これでSelect結果のみが更新されているように見えます。Oracleの場合だと、NVLを使うと同じ事ができるはずです。ただし、m_usersテーブルにtrigger関数がついている場合には、全行についてtrigger関数が動作してしまうので良くないこともあります。そのような場合には、次のexists句を使ったSQLが使えます。

相関副問い合わせ(exists句)

exists句を使って、Select結果がある行に限ってUpdateするようにしたのが下記です。これだと、余計な行まで更新しません。

update m_users
   set best_point = (
         select max(point)
           from d_records
          where user_id = m.user_id
            and test_id = 1
            and point >= m.best_point
       )
 where exists (
         select *
           from d_records
          where user_id = m.user_id
            and test_id = 1
            and point >= m.best_point
       )

当初はこのSQLで実装していたのですが、思ったよりも速度パフォーマンスが良くないことがわかりました。Select結果1行につき、副問い合わせが1回実行されるため、Select結果数が多い場合(私の例では100万件程度)にはパフォーマンスが悪くなります。ほぼ全行Selectされてしまう場合には、先のcoalesceを使った場合との違いはそれほどありませんでした。

with句 + Update From

Select結果をいったんwith句を使って評価しておいた後で、Updateするというのが解決策となりました。

with d_target_records as (
  select user_id
       , point
    from d_records d
   inner join m_users m
           on d.user_id = m.user_id
   where d.test_id = 1
     and d.point >= m.best_point
)
update m_users m
   set best_point = d.point
  from d_target_records d
 where m.user_id = d.user_id

このSQLも、Select結果が0件の場合は更新結果も0件となるので、余計な行を更新することはありません。
Update From という構文は、PostgreSQLでは使えますがOracleでは使えないので注意が必要です。

スポンサーリンク
関連キーワード
DB, PostgreSQLの関連記事
  • 【PostgreSQL】親子関係のあるレコードの serial を uuid に付け替える方法
  • 【PostgreSQL】Select結果でUpdateする3パターン
おすすめの記事