技術と魚

技術屋ですが経営もやってます

ON CONFLICT句で部分的に更新しようとしてviolates not null constraintと出てしまって困った話

(Postgresでの話)

以下の様にテーブル上に非NULL制約のあるテーブルで、INSERT ~ ON CONFLICT ~ DO UPDATE を使って部分的に更新しようとすると、エラーになってしまう。

CREATE TABLE test (id int PRIMARY KEY, nnv int not null, upd int);

INSERT INTO test (id, nnv, upd) VALUES (1, 2, 3);

INSERT INTO test (id, upd) VALUES (1, 4) ON CONFLICT (id) DO UPDATE SET upd = EXCLUDED.upd;

ERROR:  null value in column "nnv" violates not-null constraint
DETAIL:  Failing row contains (1, null, 4).

ID一致してたら更新したいだけなのに、非NULLカラム指定してない的なエラーで怒られる??なんでやねん。

と思って色々調べていたんですが、 ON CONFLICT句って一意制約によるエラーが出た場合の 例外処理 を書いてるに等しいんですね。

つまり擬似コードで書くと

try {
  insert()
} catch(e) {
  if (e is UniqueViolation) {
    update()
  } else {
    throw e
  }
}

のようなことをやっているのであって、そもそもINSERTしようとしたものに非NULLカラム指定してないわけだから、

try {
  insert(id = 1, nnv = null, upd = 4)
} catch(e) {
  if (e is UniqueViolation) {
    update()
  } else {
    throw e
  }
}

を実行しようとしているのであって、ここでいう e が UniqueViolation(一意制約違反) ではなく NotNullConstraintViolation(非NULL制約違反) だからこうなるんですね。

こう考えればまあ普通の挙動ではあるもののなんでこんなに困惑したんだっけ、と思い返すと、「ID調べて無ければ作成して存在すれば更新する」という動きを期待していたから。

つまり↓のような動きを脳内で勝手に期待していたわけですね。

try {
  record = find(id)
  record.update(rest)
} catch(e) {
  insert();
}

追記

mysqlの ON DUPLICATE KEY UPDATE の場合はこれは起きないようです。やっぱ分かんねえわ。