Schemas and Tables

Problem / Context

You’ve inherited a growing monolith database. Names collide, and accidental cross-feature queries cause bugs. You need a clean way to group objects per feature/team and create tables with safe defaults. PostgreSQL schemas give you namespaces, and well-structured tables make intent clear from day one.

Core Concept

Implementation

Follow these steps to organize with schemas and create solid tables.

1. Create a schema and a table

Create an accounting schema and a table inside it:

CREATE SCHEMA accounting;

CREATE TABLE accounting.invoices (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    customer_id INT NOT NULL,
    amount NUMERIC(10,2) NOT NULL,
    issued_at TIMESTAMPTZ DEFAULT now()
);

Outcome: a new namespace and a table with clear types and a primary key.

2. Reuse table names safely across domains

Two departments track their own employee lists without conflicts:

CREATE SCHEMA sales;
CREATE SCHEMA hr;

CREATE TABLE sales.employees (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE hr.employees (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name TEXT NOT NULL
);

Outcome: same table name, different schemas, no conflict.

3. Adopt schemas later (migration steps)

Move tables out of public with minimal risk:

  1. Create the new schema.
  2. Move tables:
  3. Update code to reference schema.table (or adjust search_path).
  4. Fix grants: ensure the app role has USAGE on the schema.
  5. Lock down public if you no longer want new objects there.

4. Permissions quick-start

Grant usage on a schema to a role and limit writes as needed:

GRANT USAGE ON SCHEMA accounting TO app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA accounting TO app_role;

Variations and Trade‑Offs

Pitfalls

Recap

Optional Exercises

  1. Create two schemas (app, audit) and move an existing table into each. Adjust search_path to test resolution.
  2. Add a role that can only SELECT from audit.*. Verify that a write fails.
  3. Rename a table while moving it to a new schema in one migration (ALTER TABLE ... SET SCHEMA ... RENAME TO ...).
  4. Create hr.employees and sales.employees, then write a query that unions them with a source column.
  5. Lock down public so no new objects can be created there. Record the GRANT statements you changed.

References