ERROR: new row for relation "appointments" violates check constraint "appointments_duration_check" (SQLSTATE 23514) means a value you inserted or updated breaks a CHECK rule on the table. The constraint name tells you which rule, and the DETAIL line shows the row that broke it. Send a value that satisfies the rule.

Look the rule up with pg_get_constraintdef, compare it with the failing row, and correct the data. Change or drop the constraint only if the rule itself is wrong.

The error

INSERT INTO appointments (patient, duration, starts_at, ends_at)
VALUES ('Maria Garcia', 0, '2026-11-10 09:00', '2026-11-10 09:30');
psql:/tmp/q8.sql:11: ERROR:  new row for relation "appointments" violates check constraint "appointments_duration_check"
DETAIL:  Failing row contains (1, Maria Garcia, 0, 2026-11-10 09:00:00, 2026-11-10 09:30:00).

A rule across two columns fails the same way, under its own name:

INSERT INTO appointments (patient, duration, starts_at, ends_at)
VALUES ('David Chen', 30, '2026-11-10 10:00', '2026-11-10 09:30');
psql:/tmp/q8.sql:14: ERROR:  new row for relation "appointments" violates check constraint "appointments_ends_after_start"
DETAIL:  Failing row contains (2, David Chen, 30, 2026-11-10 10:00:00, 2026-11-10 09:30:00).

Why it happens

A CHECK constraint is a condition every row must meet. This table has two: an unnamed column check on duration, which PostgreSQL named appointments_duration_check, and a named table check that the appointment ends after it starts. The message names the one that failed; the rule text is in the catalog:

SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'appointments'::regclass AND contype = 'c' ORDER BY conname;
            conname            |              pg_get_constraintdef
-------------------------------+-------------------------------------------------
 appointments_duration_check   | CHECK (((duration >= 5) AND (duration <= 120)))
 appointments_ends_after_start | CHECK ((ends_at > starts_at))
(2 rows)

A duration of 0 is outside 5 to 120; an end time before the start time breaks the second rule. The DETAIL row is in column order, starting with the identity value the failed insert had already drawn.

The fix

INSERT INTO appointments (patient, duration, starts_at, ends_at)
VALUES ('Maria Garcia', 30, '2026-11-10 09:00', '2026-11-10 09:30');
INSERT 0 1

Updates are checked too: UPDATE appointments SET duration = 500 on that row failed with the same error and a failing row showing 500. Note one gap: a CHECK passes when its condition is null, so on a table with CHECK (n > 0) inserting NULL succeeded while -1 failed. Add NOT NULL if a value is required. Validate the same rule in your application to give users a friendly message, and keep the constraint as the last line of defence. From .NET, Npgsql reports this as a PostgresException with SqlState 23514 and the name in ConstraintName.

How it was reproduced

A postgres:18 container (PostgreSQL 18.4, Debian build 18.4-1.pgdg13+1) on Docker 29.9.2, with an appointments table holding a column check on duration (5 to 120) and a named table check ends_at > starts_at, and fictional patients. Both bad inserts and the bad update failed as shown; the corrected insert succeeded. A verbose run printed ERROR: 23514 with a CONSTRAINT NAME field.

Frequently asked

How do I see the rule behind a PostgreSQL check constraint?
Query pg_constraint with pg_get_constraintdef(oid), filtered on conrelid = 'tablename'::regclass and contype = 'c'. Compare the rule with the failing row in the DETAIL line.
Why does my CHECK constraint allow NULL?
A CHECK fails only when its condition is false. With NULL the condition is null, not false, so the row passes. Add NOT NULL to the column if the value is required.
Does a check constraint apply to UPDATE as well as INSERT?
Yes. An UPDATE that makes a row break the rule fails with the same 23514 error and a DETAIL line showing the new row.

More decoded errors in the Fixes category. For when to use CHECK constraints in a design, see constraints in Database Design for Developers.