DEV Community

umzzil nng
umzzil nng

Posted on Originally published at oraerror.com

PostgreSQL 23P01 Error: Causes and Solutions Complete Guide

PostgreSQL Error 23P01: exclusion_violation

PostgreSQL error 23P01 exclusion_violation occurs when an INSERT or UPDATE operation conflicts with an existing row based on an Exclusion Constraint. Unlike a simple UNIQUE constraint that only checks equality, exclusion constraints support operators like && (range overlap), making them ideal for enforcing rules such as "no two reservations can overlap in time for the same room." This error is your database's way of saying: the new row you're trying to add breaks a multi-column, operator-based conflict rule.


Top 3 Causes

1. Overlapping Time Ranges (Most Common)

The classic use case for exclusion constraints is preventing time-range overlaps in scheduling or booking systems. If a new reservation overlaps with an existing one, PostgreSQL rejects it immediately.

-- Setup
CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE TABLE room_reservations (
    id          SERIAL PRIMARY KEY,
    room_id     INT NOT NULL,
    reserved_by VARCHAR(100),
    during      TSRANGE NOT NULL,
    EXCLUDE USING GIST (
        room_id WITH =,
        during  WITH &&
    )
);

-- This works fine
INSERT INTO room_reservations (room_id, reserved_by, during)
VALUES (1, 'Alice', '[2024-06-01 09:00, 2024-06-01 11:00)');

-- This triggers 23P01 — overlaps with Alice's booking
INSERT INTO room_reservations (room_id, reserved_by, during)
VALUES (1, 'Bob', '[2024-06-01 10:00, 2024-06-01 12:00)');
-- ERROR:  conflicting key value violates exclusion constraint
Enter fullscreen mode Exit fullscreen mode

2. Dirty Data During Migration

When migrating data from legacy systems that had no exclusion constraints, the source data may already contain overlapping records. Bulk-loading this data into a table with an exclusion constraint will cause the migration to fail mid-way.

-- Check for overlapping records in staging data BEFORE inserting
SELECT
    a.room_id,
    a.reserved_by AS booking_a,
    b.reserved_by AS booking_b,
    a.during AS range_a,
    b.during AS range_b
FROM staging_reservations a
JOIN staging_reservations b
  ON a.room_id = b.room_id
 AND a.ctid < b.ctid
 AND tsrange(a.start_time, a.end_time)
  && tsrange(b.start_time, b.end_time);

-- Only insert non-conflicting rows
INSERT INTO room_reservations (room_id, reserved_by, during)
SELECT DISTINCT ON (room_id, tsrange(start_time, end_time))
    room_id,
    reserved_by,
    tsrange(start_time, end_time)
FROM staging_reservations
ORDER BY room_id, tsrange(start_time, end_time);
Enter fullscreen mode Exit fullscreen mode

3. Misunderstanding the Constraint Definition

Developers sometimes misread which columns and operators are involved in the exclusion constraint, leading to unexpected failures. Always inspect the actual constraint definition before writing insert logic.

-- Inspect exclusion constraints on a table
SELECT
    conname AS constraint_name,
    pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'room_reservations'::REGCLASS
  AND contype = 'x';

-- Also check the underlying GIST index
SELECT
    i.relname AS index_name,
    pg_get_indexdef(ix.indexrelid) AS index_def
FROM pg_index ix
JOIN pg_class i ON i.oid = ix.indexrelid
WHERE ix.indrelid = 'room_reservations'::REGCLASS
  AND ix.indisexclusion = TRUE;
Enter fullscreen mode Exit fullscreen mode

Quick Fix Solutions

Handle the error gracefully in PL/pgSQL:

DO $$
BEGIN
    INSERT INTO room_reservations (room_id, reserved_by, during)
    VALUES (1, 'Charlie', '[2024-06-01 10:30, 2024-06-01 11:30)');
EXCEPTION
    WHEN exclusion_violation THEN
        RAISE NOTICE 'Booking conflict detected. Please choose a different time slot.';
END;
$$;
Enter fullscreen mode Exit fullscreen mode

Check for conflicts before inserting:

-- Safe insert pattern
INSERT INTO room_reservations (room_id, reserved_by, during)
SELECT 1, 'Diana', '[2024-06-01 13:00, 2024-06-01 14:00)'
WHERE NOT EXISTS (
    SELECT 1
    FROM room_reservations
    WHERE room_id = 1
      AND during && '[2024-06-01 13:00, 2024-06-01 14:00)'::TSRANGE
);
Enter fullscreen mode Exit fullscreen mode

Prevention Tips

1. Always validate before inserting. Run a conflict-check query before any INSERT into a table with exclusion constraints. Use the && operator directly in your pre-check query to mirror exactly what the constraint enforces. This prevents the error from reaching the database constraint layer and provides a better user experience.

2. Test constraints with realistic data. Before deploying a new exclusion constraint to production, validate it against existing data:

-- Verify no existing data conflicts before adding the constraint
SELECT COUNT(*)
FROM room_reservations a
JOIN room_reservations b
  ON a.room_id = b.room_id
 AND a.id < b.id
 AND a.during && b.during;
-- Must return 0 before the constraint can be safely added
Enter fullscreen mode Exit fullscreen mode

Integrate this check into your CI/CD pipeline and migration scripts to catch issues early.


Related Errors

Code Name Relation
23000 integrity_constraint_violation Parent class of 23P01
23505 unique_violation Equality-only version of exclusion
23502 not_null_violation Can co-occur on constrained columns
23514 check_violation Another data-integrity sibling

📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.

Top comments (0)