Thuta Learning
ProjectsData & Databasesbeginner

Project 1 — Tutorial Platform Schema

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Explain the core ideas behind Project 1 — Tutorial Platform Schema
  • Run the sample SQL or command and verify its output
  • Apply the technique to the Tutorial Platform and production scenarios

Build the mental model

The project now becomes one repeatable migration rather than isolated snippets. Create tables in dependency order and apply consistent identity keys, foreign keys, checks, uniqueness, and timestamps. The migration must build a blank database, with deliberate behavior on reruns.

Connect it to a real scenario

Put the schema and core tables in `001_initial_schema.sql`. Simple foreign keys alone may not prove that an enrollment and lesson belong to the same tutorial, so choose a composite-key design or transactional function and test the invariant.

Try the working example

sql
CREATE TABLE app.enrollments (
  enrollment_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id bigint NOT NULL REFERENCES app.users(user_id) ON DELETE CASCADE,
  tutorial_id bigint NOT NULL REFERENCES app.tutorials(tutorial_id) ON DELETE CASCADE,
  enrolled_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (user_id, tutorial_id),
  UNIQUE (enrollment_id, tutorial_id)
);

CREATE TABLE app.lesson_progress (
  enrollment_id bigint NOT NULL,
  tutorial_id bigint NOT NULL,
  lesson_id bigint NOT NULL,
  completed boolean NOT NULL DEFAULT false,
  completed_at timestamptz,
  PRIMARY KEY (enrollment_id, lesson_id),
  FOREIGN KEY (enrollment_id, tutorial_id)
    REFERENCES app.enrollments(enrollment_id, tutorial_id) ON DELETE CASCADE,
  FOREIGN KEY (tutorial_id, lesson_id)
    REFERENCES app.lessons(tutorial_id, lesson_id) ON DELETE CASCADE,
  CHECK ((completed AND completed_at IS NOT NULL) OR
         (NOT completed AND completed_at IS NULL))
);
You should see
The relational schema prevents progress from crossing tutorial boundaries.

5-minute try-it

Explain why `app.lessons` needs `UNIQUE (tutorial_id, lesson_id)`, add it, and run the migration on a blank database.

One important caution

Happy-path inserts alone do not prove the schema protects invariants; add invalid-relationship tests.

PostgreSQL — Table BasicsPostgreSQL Global Development Group

Easy traps

  • Happy-path inserts alone do not prove the schema protects invariants; add invalid-relationship tests.
  • Validate sample code on a local or test database with recoverable backups before applying it to production data.

Exercise

Explain why `app.lessons` needs `UNIQUE (tutorial_id, lesson_id)`, add it, and run the migration on a blank database.

You'll know it worked when: The relational schema prevents progress from crossing tutorial boundaries.

Project 1 — Tutorial Platform Schema | Thuta Learning