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
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);
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;
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;
$$;
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
);
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
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)