InletDownload

PostgreSQL error 23502

null value in column violates not-null constraint

A row would have NULL in a column declared NOT NULL. Either the statement left the column out and it has no default, or something sent an explicit NULL, which PostgreSQL stores as NULL even when the column has a default.

ERROR:  null value in column "email" of relation "customers" violates not-null constraint

Tested on PostgreSQL 18.6 (also 14–17) · Updated 9 October 2026

What it means

The column is declared NOT NULL and the row you’re writing has no value in it. The statement is rejected and none of its rows are written. The DETAIL line shows the whole row as it would have been stored, in the table’s column order, with null where the value is missing:

ERROR:  null value in column "email" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (1, null, active, 2026-10-09 10:13:19.77362+00).

Columns you didn’t supply already show their defaults, so you see the row exactly as the server built it.

Common causes

  1. The column was left out of the INSERT and has no DEFAULT.
  2. An explicit NULL was sent. A default only applies when the column is omitted or you write DEFAULT. Some ORMs and drivers send every column, with NULL for anything unset, so the default never applies.
  3. An UPDATE set it to NULL, often through a form field left empty, or a subquery that found nothing.
  4. INSERT … SELECT from data with gaps: a staging table, a LEFT JOIN that didn’t match.
  5. An empty field in a CSV file. With COPY … (FORMAT csv), an unquoted empty field is NULL.
  6. A NULL for an identity or serial column. Writing NULL explicitly into id doesn’t trigger the sequence; leave the column out instead.

Adding NOT NULL to an existing column that has nulls gives a different message with the same code, column "email" of relation "staging" contains null values. See setting a column NOT NULL.

How to fix it

Supply the value, or let the default apply

Leave the column out, or write DEFAULT for it, so its default is used:

INSERT INTO customers (email) VALUES ('ada@example.com');
INSERT INTO customers (email, status) VALUES ('grace@example.com', DEFAULT);

If the application sends NULL for unset fields, change it to omit them, or to send the value you want stored.

Give the column a default

If there’s a sensible value for rows that don’t say, add a default. It applies to new rows only:

ALTER TABLE customers ALTER COLUMN status SET DEFAULT 'active';

Replace nulls in INSERT … SELECT

Filter them out, or replace them with coalesce:

INSERT INTO customers (email, status)
SELECT email, coalesce(status, 'active')
FROM staging
WHERE email IS NOT NULL;

Find the bad lines in a CSV

COPY names the line it stopped on in the CONTEXT line. Fix the file, or load it into a staging table with no constraints first and clean it up with SQL. The ON_ERROR ignore option of PostgreSQL 17 and later doesn’t help here: it skips rows whose values can’t be converted to the column’s type, not rows that break a constraint.

Ask whether the column should allow nulls

If “unknown” is a real state for this value, the constraint is wrong rather than the data:

ALTER TABLE customers ALTER COLUMN phone DROP NOT NULL;

Reproduce it

On PostgreSQL 18.6:

CREATE TABLE customers (
  id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  email text NOT NULL,
  status text NOT NULL DEFAULT 'active',
  created_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO customers (status) VALUES ('active');
INSERT INTO customers (email, status) VALUES ('ada@example.com', NULL);
ERROR:  null value in column "email" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (1, null, active, 2026-10-09 10:13:19.77362+00).

ERROR:  null value in column "status" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (2, ada@example.com, null, 2026-10-09 10:13:19.77362+00).

The second insert sent an explicit NULL, so the 'active' default didn’t apply. Leaving status out, or writing DEFAULT, stores active. Each failed insert still used up an identity value (1, then 2). An explicit NULL for the identity column:

ERROR:  null value in column "id" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (null, linus@example.com, active, 2026-10-09 10:13:19.77362+00).

A CSV with an empty email on its second line, loaded into a fresh copy of the table:

COPY customers (email, status) FROM STDIN WITH (FORMAT csv);
bob@example.com,active
,active
\.
ERROR:  null value in column "email" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (2, null, active, 2026-10-09 10:22:36.61782+00).
CONTEXT:  COPY customers, line 2: ",active"

COPY is all or nothing: the good first line isn’t kept either. A serial column behaves like the identity column: INSERT INTO s (id, x) VALUES (NULL, 'a') fails with null value in column "id" of relation "s". PostgreSQL 14, 15, 16 and 17 print the same messages, including of relation "customers".

In Inlet

In the grid, Set NULL is an explicit action, and edits are staged until you commit (⌘S), with Review showing the exact SQL before anything is sent. In the query editor the error appears at the position the server reports, and with your own Anthropic API key Ask Claude (⌘L) can fix the failed statement, sending the schema, the SQL and the error, never rows.

Related

Sources