Build a Complete SQL Server Database From Scratch: A Capstone Project Walkthrough

Build a Complete SQL Server Database From Scratch: A Capstone Project Walkthrough

Everything from the fundamentals track comes together here — no new syntax, just applying what you already know (data types, DDL/DML, queries, aggregates, joins, and constraints) to a realistic, slightly underspecified brief, the way a real task at work actually arrives. This lesson gives you the brief, the schema skeleton, and the design decisions to wrestle with — not the finished answer. Building it yourself, including getting some parts wrong first, is the actual point.

The BookNook Schema, Sketched(five tables, one junction)Authorauthor_id (PK)Bookauthor_id (FK)CustomerOrdercustomer_id (FK)Customercustomer_id (PK)OrderItemPK (order_id, book_id)the junction tableno dupe linesFKFKFKFKGotcha: OrderItem.unit_price deliberately DUPLICATES Book.price —a historical order should show what was paid, not today’s price.That’s denormalization on purpose, straight from Chapter 6.

The Brief: BookNook

Design and build a database for a small online bookstore. It needs to track books, authors, customers, and orders.

  • Author — name, country
  • Book — title, price, publish_year, foreign key to Author
  • Customer — name, unique email
  • CustomerOrder — customer_id (FK), order_date, status
  • OrderItem — the junction table connecting orders to books, since an order can contain many books and a book can appear in many orders

The Schema, Visualized

Author Book OrderItem CustomerOrder Customer

OrderItem is the piece most beginners miss on their first attempt — a many-to-many relationship (Book ↔ Order) always resolves through a junction table like this, never a direct link between the two. This is exactly the Enrollment pattern from Chapter 5, applied to a new domain.

A Skeleton to Start From — You Fill In the Constraints

Deliberately incomplete: the columns are given, but the exact PK/FK/CHECK/DEFAULT choices are yours to decide and justify, based on everything Chapters 2 and 6 covered.

CREATE TABLE dbo.Author (
    author_id   INT IDENTITY(1,1) PRIMARY KEY,
    full_name   NVARCHAR(100) NOT NULL,
    country     NVARCHAR(50)  NOT NULL
);

CREATE TABLE dbo.Book (
    book_id       INT IDENTITY(1,1) PRIMARY KEY,
    title         NVARCHAR(200) NOT NULL,
    author_id     INT NOT NULL REFERENCES dbo.Author(author_id),
    price         DECIMAL(8,2)  NOT NULL, -- what CHECK belongs here?
    publish_year  INT NOT NULL
);

CREATE TABLE dbo.Customer (
    customer_id  INT IDENTITY(1,1) PRIMARY KEY,
    full_name    NVARCHAR(100) NOT NULL,
    email        NVARCHAR(100) NOT NULL -- what constraint makes this genuinely unique?
);

CREATE TABLE dbo.CustomerOrder (
    order_id     INT IDENTITY(1,1) PRIMARY KEY,
    customer_id  INT NOT NULL REFERENCES dbo.Customer(customer_id),
    order_date   DATE NOT NULL, -- what DEFAULT saves you typing this every time?
    status       NVARCHAR(20) NOT NULL -- what DEFAULT status makes sense for a brand-new order?
);

CREATE TABLE dbo.OrderItem (
    order_id    INT NOT NULL REFERENCES dbo.CustomerOrder(order_id),
    book_id     INT NOT NULL REFERENCES dbo.Book(book_id),
    quantity    INT NOT NULL, -- what CHECK prevents a nonsensical quantity?
    unit_price  DECIMAL(8,2) NOT NULL,
    PRIMARY KEY (order_id, book_id) -- why a composite key here, specifically?
);

A Real Design Decision You’ll Have to Make

Should OrderItem.unit_price duplicate Book.price, or should you just JOIN to Book for the price at query time? Prices change over time — what should an order from six months ago show, today’s price or the price actually paid at purchase? This is a genuine, common denormalization decision (echoing Chapter 6’s normalization lesson) — not a mistake to avoid. The right answer here is almost certainly to duplicate it: a historical order should show what was actually paid, not today’s price. Storing it directly on OrderItem is deliberate denormalization for a good reason, exactly the kind of exception the normalization lesson told you to expect.

What Your Submission Needs

  1. All five CREATE TABLE statements with appropriate PK/FK/CHECK/DEFAULT constraints — fill in every blank left above, with a one-line comment justifying each constraint choice
  2. Realistic sample data — at least 4 authors, 8 books, 5 customers, 6 orders, 10 order items
  3. A query showing each customer’s total spend across all orders (needs JOIN + GROUP BY + SUM)
  4. A query showing the best-selling book by total quantity ordered (needs JOIN + GROUP BY + SUM + ORDER BY + TOP)
  5. A query showing authors who’ve never had a book ordered — careful with the LEFT JOIN + WHERE trap from Chapter 5
Common mistake to watch for yourself making: Query #5 (authors never ordered) is a two-hop LEFT JOIN — Author to Book to OrderItem — and it’s very easy to accidentally write a WHERE clause on OrderItem that silently turns your LEFT JOINs back into INNER JOINs, making every author with zero orders vanish from the result instead of showing up with NULLs. If your result set looks suspiciously short, this is the first thing to check.

Self-Check Before You Consider It Done

Check Why it matters
Try inserting an OrderItem with a book_id that doesn’t exist Confirms your FK constraint actually works, not just that it compiles
Try inserting a negative price or zero quantity Confirms your CHECK constraints catch nonsensical values
Run query #5 and manually verify one “never ordered” author against your raw data The single best way to catch the LEFT JOIN + WHERE bug before it ships

Stretch Goal: Deploy It for Real

Everything above works identically on your local install — but try creating this exact database on Azure SQL Database or AWS RDS (Chapter 0) instead of locally. All the same CREATE TABLE and INSERT statements work unchanged; only how you connect changes.

What comes next: Once this capstone is genuinely working — constraints tested, all five queries returning correct results you’ve manually verified — you have everything SQL Server for Developers & DBAs assumes you already know. That course picks up exactly here: stored procedures, functions, triggers, transactions, and real performance tuning against schemas like this one.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server Fundamentals, coming soon on this site. Finished this capstone? You’re ready for SQL Server for Developers & DBAs.