📝
postgresql の insert on conflict で not null 制約エラーが起きる
概要
PostgreSQLのテーブルレコードを更新するために、INSERT ON CONFLICTを使おうと思った。
INSERT ON CONFLICTを使えばレコードがない場合でもある場合でも便利にデータが追加、更新できて良いと思った。
しかし、INSERT ON CONFLICTでは、更新の前にINSERTの制約チェックが入るため、NOT NULL制約を持つフィールドは必ず値を指定しないといけない事がわかった。
レコードがあればUPDATEを、ない場合はINSERTするしか無いっぽい。
少し詳細
以下のような仕様の関数を作ると便利かと考えた。
- primary keyを指定すれば、そのprimary keyを持つレコードの値を更新できる。
- この時レコードが存在する場合は任意のフィールドを更新することができる。
- primary keyを指定しないか、なければレコードが追加できる。
素のSQLだとあまり有り難みがないが、ORMを使って任意のフィールドを動的に与えられる場合には便利だと考えた。
仕様はこんなイメージ
def upsert_record_by_id(
table: table_class, data: dict, id: Optional[UUID] = None
):
"""指定されたテーブルに対してupsertを実行します。
idの設定があればそのレコードを、ない場合は新規にinsertします。
Args:
table (table_class): 操作するテーブルのクラス。
data (dict): テーブルに設定するデータ。
id (Optional[UUID]): レコードのid。更新したい場合は指定する。
"""
...
SQLで試すと以下のような感じ。
-- INIT database
DROP TABLE IF EXISTS Product;
CREATE TABLE Product (
ProductID SERIAL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
Description VARCHAR(255)
);
-- Insert a first record.
INSERT INTO Product(Name, Description) VALUES ('book','a book for IT.');
-- Update a record. SUCCESS
-- Descriptionだけ更新したいがNOT NULL制約を持つNameも指定しないといけない
INSERT INTO Product(ProductID, Name, Description) VALUES (1, 'book', 'a book for Cooking.')
ON CONFLICT(ProductID) DO UPDATE SET Description=Excluded.Description;
-- Update a record. FAILURE
-- Nameを指定しない場合は、 「SQLエラー [23502]: ERROR: null value in column "name" of relation "product" violates not-null constraint」となる。
INSERT INTO Product(ProductID, Description) VALUES (1, 'a book for Cooking.')
ON CONFLICT(ProductID) DO UPDATE SET (Name, Description)=(Excluded.Name, Excluded.Description);
-- QUERY database
SELECT * FROM Product;
参考
PostgreSQL: Re: BUG #16706: insert into on conflict(pk) do update error violates not-null constraint
I don't think that would work, because the not null constraint is checked before the INSERT, not the UPDATE.
Discussion