Incident brief
Relation already exists
A CREATE TABLE named a relation that already exists. PostgreSQL refused to create a second object under the same name instead of silently overwriting it.
In 10 seconds
- What
- Relation already exists
- What triggers it
- CREATE TABLE with a name that already exists in the current schema.
- The fix
- Use CREATE TABLE IF NOT EXISTS if the script is meant to be safely re-runnable.
- Proof
- Reproduced on PostgreSQL 16.14 → Creating a table with a name that already existed was rejected with SQLSTATE 42P07. The table was confirmed unchanged afterward.
The fix
What to do right now
The immediate, application-level response to this error.
- Use CREATE TABLE IF NOT EXISTS if the script is meant to be safely re-runnable.
- Otherwise, rename the new table or drop the old one first, deliberately.
- Don't assume IF NOT EXISTS updates an existing table's columns — see the premium test for what it actually does.
CREATE TABLE IF NOT EXISTS inventory.widgets (id serial PRIMARY KEY, note text);Diagnose
See it live on the server
Run these against the affected instance to confirm the diagnosis before you act.
Standard triage — not specific to this error
These are canonical PostgreSQL system-catalog queries, shown as SQL to run. No sample output is attached because this is general triage, not a captured lab transcript.This SQLSTATE does not have an error-specific live snapshot yet. These are the canonical system-catalog queries you run against the affected server to see the problem in real time — standard triage, not a reproduced transcript.
What is running right now
Active backends, how long each has been running, and what it is waiting on.
SELECT pid,
state,
wait_event_type,
wait_event,
now() - query_start AS running_for,
left(query, 80) AS query
FROM pg_stat_activity
WHERE state <> 'idle'
AND pid <> pg_backend_pid()
ORDER BY running_for DESC NULLS LAST;Who is blocking whom
Turn raw blocking PIDs into the actual queries on both sides of the wait.
SELECT blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query
FROM pg_stat_activity AS blocked
JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b(pid) ON true
JOIN pg_stat_activity AS blocking ON blocking.pid = b.pid
WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0;Locks that are still waiting
Every lock a backend has requested but not yet been granted.
SELECT l.pid,
l.locktype,
l.mode,
l.granted,
COALESCE(c.relname, l.transactionid::text) AS object
FROM pg_locks l
LEFT JOIN pg_class c ON c.oid = l.relation
WHERE NOT l.granted
ORDER BY l.pid;Why it happens
What PostgreSQL is telling you
The mechanism behind the error, grounded in the official manual — not paraphrased.
PostgreSQL 16 Documentation — CREATE TABLE, IF NOT EXISTS
Do not throw an error if a relation with the same name already exists. A notice is issued in this case. Note that there is no guarantee that the existing relation is anything like the one that would have been created.Read the full section on postgresql.org →
The duplicate CREATE TABLE
PostgreSQL found an existing relation with the same name in the current schema and refused to create a second one under it.Confirming the original table is unchanged
The original table is exactly as it was — empty, with no side effect from the rejected CREATE TABLE.Reproduce & verify
A real, single-session PostgreSQL reproduction
A literal transcript of SQL run against a live PostgreSQL instance in an isolated lab — the commands below are exactly what was executed.
- 1CREATE TABLE with a name that already exists in the current schema.
- 2PostgreSQL refuses to create a duplicate relation.
- 3SQLSTATE 42P07 is reported, naming the exact relation.
One client: create a table, then try to create another table with the same name.
DROP TABLE IF EXISTS inventory.widgets;
CREATE TABLE inventory.widgets (id serial PRIMARY KEY, note text);CREATE TABLE inventory.widgets (id serial PRIMARY KEY, note text);SELECT count(*) FROM inventory.widgets;What PostgreSQL actually returned
DROP TABLE
CREATE TABLEERROR: relation "widgets" already exists count
-------
0
(1 row)The IF NOT EXISTS fix was tested directly against the same duplicate-name scenario.
Re-run the same CREATE TABLE, this time with IF NOT EXISTS.
Without this
Above: a second CREATE TABLE with the same name is rejected.
With this, tested
Below: the same CREATE TABLE, with IF NOT EXISTS, succeeds with a notice instead of an error.
What Pro unlocks here
- The exact prevention SQL — copy-paste ready
- Raw psql output captured from the Docker lab
- A senior-DBA action list to take it further
- Live monitoring queries to catch it in production
- The deeper audit: fix-that-fails counterexample, GUC before/after, server-log evidence
Related & next steps
Follow the thread
Everything this error touches — jump straight to the sibling error, term, runbook, or parameter.
Related errors
Verification
- Last verified
- 2026-07-15 (Docker lab, PostgreSQL 16.14)
- Reviewed by
- Verified against PostgreSQL 16.14 in an isolated lab environment
- Audit status
- reviewed