PostgresqlのCardinalityViolationエラーの解決方法
Rails6で実装されたActiveRecord#upsert_all
でバルクアップデートをしようとすると
ActiveRecord::StatementInvalid: PG::CardinalityViolation: ERROR: ON CONFLICT DO UPDATE コマンドは行に再度影響を与えることはできません HINT: 同じコマンドでの挿入候補の行が同じ制約値を持つことがないようにしてください
とエラーが出たけどCardinalityViolation
の意味がわからなかったので調べてみました。
CardinalityViolation とは
まず cardinality という英語の意味がわからなかったので調べてみたところ数学用語らしい。
数学、とくに集合論において、濃度、カーディナリティ(のうど、英: cardinality)とは、有限集合における「元の個数」を一般の集合に拡張したものである。集合の濃度は基数 (cardinal number) と呼ばれる数によって表される。
なんのこっちゃわからなかったので Cardinality Violation
で調べてみるとそれらしいのが出てきた。
Cardinality violations occur when a query that should return only a single row returns more than one row to an Embedded SQL™ application.
一行を返すべき時に複数行返すと起きるエラーらしい。
The PG::CardinalityViolation exception occurs when a row cannot be updated a second time in the same ON CONFLICT DO UPDATE SQL query. PostgreSQL assumes this behavior would lead the same row to updated a second time in the same SQL query, in unspecified order, non-deterministically. PostgreSQL recommends it is the developer's responsibility to prevent this situation from occurring.
このPostgresqlのエラーはON CONFLICT DO UPDATE
で同じ行が2度めのUPDATEをかけられた時に起きるとのこと。つまりにupsert_all
にわたすハッシュの配列ないに同一の一意条件を持つとこのエラーが起きる。
例を上げて説明すると以下のようにtitle
が unique なBook
モデルでupsert_all
を使うとAuthor2
とAuthor3
どちらも更新を試みるのでエラーになるわけです。upsert_all
に渡すハッシュの配列の一意条件が重複しないように取り計らいましょう。
Book.create(title: 'Book1', author: 'Author1')
Book.upsert_all([
{title: 'Book1', author: 'Author2'}
{title: 'Book1', author: 'Author3'}
], unique_by: :title
)