エラーの内容
INSERT または UPDATE を実行すると、PostgreSQL が即座にエラーを返します:
ERROR: null value in column "user_id" of relation "orders" violates not-null constraint
DETAIL: Failing row contains (null, 101, 2024-01-15).
メッセージは簡潔ですが有用です。user_id に NULL を書き込もうとしましたが、そのカラムには NOT NULL 制約があります。つまり、いかなる例外もなく、常に実際の値が必要です。
なぜこのエラーが発生するのか
いくつかのシナリオでこのエラーが発生します。最もよくあるケースは次のとおりです:
INSERT文でカラムを完全に省略しており、DEFAULTも定義されていない — PostgreSQL には代わりに使える値がありません。- アプリケーションコードが事前にバリデーションを行わず、
None/null/undefinedをそのままクエリに渡している。 - 誰かが既存のカラムに
NOT NULL制約を追加したが、古いアプリコードはそれを知らず、そのフィールドをスキップし続けている。 - ORM モデルやマイグレーションスクリプトが実際のテーブルスキーマと乖離している。
ステップ 1 — テーブルスキーマを確認する
何かを変更する前に、どのカラムが実際に NOT NULL を強制しているか確認しましょう。psql で実行:
\d orders
または、pgAdmin や DBeaver などあらゆるクライアントで使えるこのクエリを利用する:
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'orders'
ORDER BY ordinal_position;
is_nullable = 'NO' かつ column_default が空の行に注目してください。そのカラムはすべての INSERT で明示的な値が必要であり、デフォルト値による救済はありません。
ステップ 2 — ステートメントのバグを特定する
INSERT 文とスキーマの出力を並べて比較しましょう。このエラーの大半は 2 つのパターンから発生します。
1 つ目:カラムを完全に省略しているケース:
-- user_id が抜けている — PostgreSQL は NULL を受け取る
INSERT INTO orders (product_id, order_date)
VALUES (101, '2024-01-15');
2 つ目:明示的に NULL を渡しているケース:
INSERT INTO orders (user_id, product_id, order_date)
VALUES (NULL, 101, '2024-01-15'); -- 即座に拒否される
いずれの場合も、PostgreSQL は NULL を受け付けないカラムに NULL が書き込まれようとしているのを検知します。
ステップ 3 — 修正方法を選択する
オプション A:不足している値を指定する(約 80% のケースに対応)
必要なカラムに実際の値を含めるだけです:
INSERT INTO orders (user_id, product_id, order_date)
VALUES (42, 101, '2024-01-15');
アプリケーションコードから INSERT する場合は、クエリ実行後ではなく、実行前にバリデーションを行いましょう:
# Python の例
if user_id is None:
raise ValueError("user_id is required before inserting an order")
cursor.execute(
"INSERT INTO orders (user_id, product_id, order_date) VALUES (%s, %s, %s)",
(user_id, product_id, order_date)
)
オプション B:カラムに DEFAULT を追加する
合理的なデフォルト値がある場合はどうでしょうか?たとえば、自動システムによる注文は常にユーザー 0 に紐付けるケースです。カラムに一度だけ設定します:
ALTER TABLE orders
ALTER COLUMN user_id SET DEFAULT 0;
これにより、user_id を省略した INSERT はエラーを出さずに 0 を使用するようになります。
オプション C:NOT NULL 制約を削除する(NULL が本当に有効な場合のみ)
ゲストチェックアウトはその好例です。注文にユーザーが紐付かないことが正当にありえます。その場合は:
ALTER TABLE orders
ALTER COLUMN user_id DROP NOT NULL;
エラーを黙らせるためだけにこれを行わないでください。NULL がデータモデルにおいて実際の意味を持つ場合にのみ実施しましょう。
オプション D:NOT NULL を適用する前に既存の行をバックフィルする
既にデータが存在するテーブルに新しい NOT NULL カラムを追加する場合、既存の行に NULL が入っているためマイグレーションがブロックされます。先にバックフィル、次に制約の適用という順序で行います:
-- ステップ 1:カラムを追加し、いったん NULL を許可する
ALTER TABLE orders ADD COLUMN user_id INTEGER;
-- ステップ 2:既存の全行に実際の値を設定する
UPDATE orders SET user_id = 0 WHERE user_id IS NULL;
-- ステップ 3:制約を適用する
ALTER TABLE orders ALTER COLUMN user_id SET NOT NULL;
ステップ 2 をスキップすることが、このエラーに関するマイグレーションで最もよく見られるミスです。
ステップ 4 — 修正を確認する
RETURNING * を付けて元のステートメントを再実行します:
INSERT INTO orders (user_id, product_id, order_date)
VALUES (42, 101, '2024-01-15')
RETURNING *;
成功すると PostgreSQL が挿入した行を返します。エラーが出なければ制約は満たされています。データが正しく保存されたか確認しましょう:
SELECT * FROM orders WHERE product_id = 101 ORDER BY order_date DESC LIMIT 5;
今後の予防策
- クエリの中ではなく、実行前にバリデーションを行う —
None/nullはアプリケーション層で排除しましょう。値がデータベースに到達する時点では、すでにクリーンな状態であるべきです。 - INSERT では常に
RETURNINGを使用する — 書き込みの成功を確認し、保存された内容を正確に把握できます。低コストの保険です。 - 制約を適用する前にバックフィルする — デフォルト値なしで稼働中のテーブルに
NOT NULLを追加すると、既存の行にNULLがある場合は即座に失敗します。上記の 3 ステップのマイグレーションが安全なパターンです。 - ORM モデルを同期させ続ける — マイグレーションのたびに、SQLAlchemy・Django ORM・Prisma のモデルが実際のスキーマを反映しているか確認しましょう。モデルとテーブルの乖離はサイレントなバグの温床です。
- エラーメッセージをよく読む — PostgreSQL はエラー内にカラム名(
"user_id")とテーブル名("orders")を明示しています。スキーマ全体を眺めるのではなく、そのカラムに直接アクセスしましょう。

