This hands-on tutorial demonstrates how to use schemas to organize data and objects you create, a useful technique for storing a collection of learning resources in a single database.

:wrench: Requirements: You should know how to create a new database and connect to it with psql.

Beginners are encouraged to try these examples (in a new database, of course) and experiment on their own.

Organizing with Schemas

:closed_book: eponymous
        (of a thing) named after a particular person. “Roseanne’s eponymous hit TV series”

Your Eponymous Schema

If you create a schema with the same name as your PostgreSQL user account, new objects will be created there instead of the public schema.

For example, create a table in a new database:

> create table t1(i int);

Then create a schema with the same name as the current user:

> select current_user;
 current_user
──────────────
 stan

> create schema stan;

Create another table:

> create table t2(t text);

List relations:

> \d
       List of relations
 Schema │ Name │ Type  │ Owner
────────┼──────┼───────┼───────
 public │ t1   │ table │ stan
 stan   │ t2   │ table │ stan

:bulb: Commands starting with \ are meta commands which are processed by psql. You can learn more about them by entering \?.

The second table was created in the schema we just created. Taking advantage of this default behavior is an easy way to keep new objects organized.

To move the first table we created from public to our renamed schema, use the ALTER TABLE command:

> alter table t1 set schema example1;

When you finish working on a project, rename the schema to something appropriate:

> alter schema stan rename to example1;

You can (should) also add a description:

> comment on schema example1 is 'using schemas: renamed schema';

List schemas with comments by adding “+” to the standard meta command:

> \dn+
                              List of schemas
   Name   │  Owner   │  Access privileges   │          Description
──────────┼──────────┼──────────────────────┼───────────────────────────────
 example1 │ stan     │                      │ using schemas: renamed schema
 public   │ postgres │ postgres=UC/postgres↵│ standard public schema
          │          │ =UC/postgres         │

Before beginning a new project, simply create a new eponymous schema. Alternatively, if you don’t want to keep what you were working on, instead of renaming it just use the DROP command to delete your schema before creating a new one.


Random photo: Street corner in Cappadocia

Understanding the search path

The search_path determines the visibility of schemas:

> show search_path;
   search_path
─────────────────
 "$user", public

By default, the $user variable is the first schema in the default search_path, which is why new objects are automagically created in your eponymous schema if it exists.

Because our renamed schema (example1) is not in the search_path, its tables aren’t visible:

> \d
Did not find any relations.

However, despite error messages to the contrary, the tables still exist and can be accessed with a qualified name (which includes the schema):

> select * from t1;
ERROR:  relation "t1" does not exist
LINE 1: select * from t1;
                      ^
> select * from example1.t1;
 i
───
(0 rows)

Changing the search path

Changing the search_path is an easy way to work with a renamed schema without the hassle of using qualified names.

For example, to make the tables in example1 visible:

> set search_path to example1;
> \d
        List of relations
  Schema  │ Name │ Type  │ Owner
──────────┼──────┼───────┼───────
 example1 │ t1   │ table │ stan
 example1 │ t2   │ table │ stan

Don’t worry. The setting is temporary. Feel free to experiment with it. The normal settings will be restored if you reconnect to the database or use the RESET command:

> reset search_path;

Recapping

To summarize, to organize various learning projects in a single database:

  • Create an eponymous schema where new objects will be created.
  • Rename and add a description to the schema when finished with a project.
  • To work with a renamed schema include its name in the search_path.

That’s that not all…

Here’s some other, optional techniques you can use with this workflow.

Random photo: Mural in Istanbul

List tables/views in all schemas

Normally, meta commands like \d only show visible objects. While it’s possible to use wildcard characters (* and ?) to list objects outside of the search path, it’s not always easy.

Instead, I use a view to list tables and views from all schemas in a database:

No need for wildcards now:

> table ls;
  schema   relation  type   total size              comment
──────────┼──────────┼───────┼────────────┼──────────────────────────────────
 example1  t1        table  0 bytes     
 example1  t2        table  8192 bytes  
 public    ls        view   0 bytes     list relations in all schemas

:sparkles: TABLE is a shortcut for SELECT * FROM.

To keep the view visible while working with another schema, add public to the search_path:

> set search_path to example1, public;

Protecting the public from yourself

While it’s not really convenient or necessary for a personal database, we can take this method even further by making it harder (for yourself) to create things in the public schema.

The easiest method is to delete the public schema. There’s nothing wrong with that approach, but you should probably keep it around for testing and experiments while you’re still learning.

A more educational exercise is revoking some or all privileges from the public schema. Such restrictions can be applied to yourself if you’re using an account with limited superpowers:

> set role to dba;
SET
# revoke create on schema public from public;
REVOKE
# reset role;
RESET
> create table test_priv(i int);
ERROR:  permission denied for schema public

An easier method is altering the database to remove public from its default search path:

> alter database stan set search_path to "$user";
ALTER DATABASE

(Unlike changes made with the SET command, this change will persist but won’t effect the current session. Reconnect to the database to observe the change.)

With this method, when my eponymous schema doesn’t exist, trying to create a new object returns an error:

> create table path_test(i int);
ERROR:  no schema has been selected to create in
LINE 1: create table path_test(i int);

But if I want to create an object in the public schema, I can still use its qualified name:

> create table public.path_test(i int);
Random photo of my cat

Further reading

PostgreSQL Documentation
Chapter 5. Data Definition : Schemas - A brief but thorough explanation of schemas and their common use cases, well worth reading.
Other
Jonathan Katz: Demystifying Schemas & search_path through Examples - A detailed exploration of schemas, roles, privileges, and security implications.