exit lab
Backend Engineering
Level 3·database·guide·intermediate

PostgreSQL Schema and Indexing Fundamentals

Model constrained relational data, choose indexes from query shapes, read execution plans, and compare important Oracle differences.

60 minutes PostgreSQL 16+ syntax, verified against PostgreSQL 18; Oracle 19c differences noted Updated 2026-07-16
PostgreSQL 16+SQLOracle 19c

Learning objectives

  • Use database constraints to protect business invariants
  • Match composite indexes to filters, ordering, selectivity, and real execution plans

What it is

Schema design defines valid durable states; indexing defines additional data structures the optimizer may use to reach those states efficiently. Constraints are correctness controls. Indexes are workload-dependent performance and access-path tools.

Why it matters

Application validation can be bypassed by races, scripts, imports, or another service. Database constraints close that gap. Poor indexes can waste storage and write throughput while still missing the actual filter and ordering pattern.

How it works

Constraint/indexPrimary job
PRIMARY KEYStable row identity and uniqueness
FOREIGN KEYReferenced-row integrity
UNIQUEBusiness uniqueness under concurrency
CHECKRow-local valid-state rule
NOT NULLRequired value
B-tree indexEquality, ranges, and ordered access
Partial indexIndex only rows matching a predicate
Expression indexIndex a deterministic expression used by queries

For a multicolumn B-tree, leading equality constraints and then the first range constraint usually determine how much of the index can bound the scan. PostgreSQL can apply later-column conditions, but they may not reduce the scanned portion in the same way.

Simple example

task-schema.sql
CREATE TABLE task (
    id           bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    owner_id     bigint NOT NULL REFERENCES app_user(id),
    title        varchar(160) NOT NULL CHECK (length(trim(title)) > 0),
    status       varchar(20) NOT NULL CHECK (status IN ('OPEN', 'DONE', 'CANCELLED')),
    due_at       timestamptz,
    created_at   timestamptz NOT NULL DEFAULT now(),
    updated_at   timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX task_owner_status_created_idx
    ON task (owner_id, status, created_at DESC, id DESC);

Backend example

keyset-page.sql
SELECT id, owner_id, title, status, created_at
FROM task
WHERE owner_id = :ownerId
  AND status = :status
  AND (created_at, id) < (:cursorCreatedAt, :cursorId)
ORDER BY created_at DESC, id DESC
LIMIT :pageSize;

The index begins with the equality predicates, then matches the descending pagination order and unique id tie-breaker. Verify with representative data and EXPLAIN (ANALYZE, BUFFERS) in a safe non-production environment; ANALYZE executes the statement.

Production example

  1. 1

    Start from queries

    Collect the exact predicates, sort order, selected columns, frequency, and latency objective.

  2. 2

    Inspect estimates

    Compare estimated and actual rows, scan type, loops, buffers, sorts, and join strategy.

  3. 3

    Change one thing

    Add or revise a constraint/index/query and re-measure with representative distribution.

  4. 4

    Account for writes

    Every index consumes space and must be updated by INSERT, UPDATE, DELETE, vacuum, backup, and replication work.

PostgreSQLOracle note
GENERATED ... AS IDENTITYIdentity is also available; sequences remain common
LIMITUse FETCH FIRST / OFFSET in modern Oracle
timestamptzOracle timestamp-with-time-zone semantics differ; test driver mappings
EXPLAIN (ANALYZE, BUFFERS)Use DBMS_XPLAN and execution-plan tooling
Partial indexesNo direct identical feature; function-based/conditional strategies differ

Common mistakes

  • Relying only on application validation for uniqueness
  • Adding an index for every column
  • Using SELECT * in stable mapper contracts
  • Assuming an index will always be chosen
  • Running EXPLAIN ANALYZE for a write on production without a rollback plan
  • Using offset pagination for unbounded deep pages

Best practices

  • Name constraints and indexes clearly
  • Prefer narrow stable keys and explicit foreign-key actions
  • Keep statistics current and inspect actual row-count errors
  • Use representative data volume and distribution
  • Design online/backward-compatible migrations
  • Test backup restore, not only backup creation

Trade-offs

ChoiceBenefitCost
NormalizeStronger consistency and less update duplicationMore joins and model navigation
DenormalizeSimpler/faster targeted readsSynchronization and stale-data risk
Covering INCLUDE indexPotential index-only readsLarger index and more write work
Soft deleteRecovery/audit convenienceEvery uniqueness and query rule becomes more complex

Interview questions

  • Why does a UNIQUE constraint solve a race that a pre-check cannot?
  • How does column order affect a composite B-tree index?
  • What evidence do you read in EXPLAIN ANALYZE?
  • When would a sequential scan be correct?
  • How does keyset pagination work with duplicate timestamps?

Hands-on task

  1. 1

    Model

    Create users, roles, tasks, orders, and order items with named constraints and audit columns.

  2. 2

    Load

    Generate data with realistic skew across owners and statuses.

  3. 3

    Measure

    Compare no index, a poorly ordered index, and a query-aligned composite index using plans and timings.

  4. 4

    Race

    Run two concurrent inserts for the same business key and confirm the constraint is the final arbiter.

References

  • PostgreSQL Global Development Group: PostgreSQL Constraints — https://www.postgresql.org/docs/18/ddl-constraints.html
  • PostgreSQL Global Development Group: PostgreSQL Multicolumn Indexes — https://www.postgresql.org/docs/18/indexes-multicolumn.html
  • PostgreSQL Global Development Group: Using EXPLAIN — https://www.postgresql.org/docs/18/using-explain.html
  • PostgreSQL Global Development Group: Transaction Isolation — https://www.postgresql.org/docs/18/transaction-iso.html
  • Oracle: Oracle Database SQL Language Reference 19c — https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/

Retrieval check

Before you continue

  • Explain the main idea in your own words without rereading the page.
  • Name one production problem this knowledge helps prevent.
  • Describe where you would apply it in a Spring Boot project.

Source metadata

Primary references checked on 2026-07-16. Version scope is stated at the top of this page.