PostgreSQL Databases, Schemas and search_path
`\dt` says the database has no tables. It has one - in a schema that is not on the search path. This is the single most common source of "my table has vanished" in PostgreSQL.
Foundations Guide 4 of 47 Beginner
- OSUbuntu 26.04 LTS (resolute)
- PostgreSQL18.6-0ubuntu0.26.04.1
- PackagingDebian cluster layout
- TimeAbout 14 min
- Reviewed27 August 2026
Written against the versions above. From PostgreSQL 15 the `public` schema is no longer writable by every user by default, which is a security improvement and another reason new tables end up somewhere unexpected.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| db-a01 | 192.168.0.81 | Ubuntu 26.04 LTS | Primary / Source / Replica Set Member 1 | 2 Core | 4 GB | 50 GB |
Before you start
- A role with
CREATEDB.
-
Create a database and see where you land
createdbis a shell wrapper aroundCREATE DATABASE, usable because the role hasCREATEDB.Connecting to it,
current_database()is appdb andcurrent_schema()is public. A database contains schemas, and schemas contain tables - two levels of namespace where MySQL has one. MySQL's "database" is much closer to PostgreSQL's *schema* than to its database, which is why the two vocabularies trip people up in both directions.bash Example session createdb appdb ; echo "exit=$?"exit=0psql -d appdb -c "SELECT current_database(), current_schema(), version()" current_database | current_schema | version------------------+----------------+--------------------------------------------------------------------------------------------------------------------------------- appdb | public | PostgreSQL 18.6 (Ubuntu 18.6-0ubuntu0.26.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0, 64-bit(1 row)Expected result
exit=0, thenappdbandpublic.Success conditionYou have a database and know which schema you are in.
-
Read the search path
"$user", public.Two entries.
$userexpands to a schema named after the connected role - which does not exist here, and is silently skipped. Thenpublic, which does.The search path decides where an unqualified name is looked up and where a
CREATE TABLEwithout a schema puts the table. It is the reason the next step behaves as it does.bash Example session psql -d appdb -c "SHOW search_path" search_path----------------- "$user", public(1 row)Expected result
"$user", public.Success conditionYou know which schemas are consulted for unqualified names.
-
Create a schema and a table in it
A schema for a subject area -
sales- owned by the role that will use it.\dnlists both schemas. Notepublicis owned bypg_database_owner, a role that exists to make ownership follow whoever owns the database.The table is created fully qualified as
sales.regions, so it lands insalesrather than wherever the search path points.serialis PostgreSQL's auto-incrementing integer, andtextis a first-class type with no length penalty - both are idiomatic here in a way they are not in MySQL.bash Example session psql -d appdb -c "CREATE SCHEMA sales AUTHORIZATION sysadmin"CREATE SCHEMApsql -d appdb -c "\dn" List of schemas Name | Owner--------+------------------- public | pg_database_owner sales | sysadmin(2 rows)psql -d appdb -c "CREATE TABLE sales.regions (id serial PRIMARY KEY, name text NOT NULL)"CREATE TABLEExpected result
CREATE SCHEMA, two schemas listed, thenCREATE TABLE.Success conditionYou have a table in a schema of its own.
-
Watch the table disappear
\dtreturns "Did not find any tables".The table exists. It was created successfully a second ago. But
\dtlists tables on the search path, and the path is"$user", public-salesis on neither.\dt sales.*finds it immediately, owner and all.This is the most common confusion in PostgreSQL and it produces a genuinely alarming message. Nothing is missing; you are looking in the wrong namespace. The fixes are to qualify the name, or to put the schema on the path with
SET search_path TO sales, publicfor the session orALTER ROLE ... SET search_pathpermanently.bash Example session psql -d appdb -c "\dt"Did not find any tables.psql -d appdb -c "\dt sales.*" List of tables Schema | Name | Type | Owner--------+---------+-------+---------- sales | regions | table | sysadmin(1 row)Expected result
Did not find any tables, then the same table listed undersales.Success conditionYou can diagnose a table that appears to be missing.
Troubleshooting
ERROR: relation "x" does not existon a table you just created.Why: It is in a schema not on the search path.
Fix:
SELECT schemaname, tablename FROM pg_tables WHERE tablename = 'x'finds it regardless of path.Tables land in
publicwhen you wanted them elsewhere.Why: Unqualified
CREATE TABLEuses the first writable schema on the path.Fix:Qualify the name, or set the search path for the role or the session.
permission denied for schema publicon PostgreSQL 15+.Why:
publicis no longer writable by all roles by default.Fix:Grant explicitly, or give each role its own schema - which is what
"$user"on the search path was designed for.