Design the Store
Advanced · 100 min build · 🏆 milestone project
You have finished Part III. Now you will design a tiny store database you would trust with checkout data: tables, constraints, seed rows, and one transactional purchase.
This is a single-session project. You will not simulate concurrent buyers in the playground; 12.2 and 12.3 showed why those interleavings need real two-session transcripts. Your job here is to build the schema and the checkout transaction that those concurrency lessons can reason about.
What you'll build
You will produce a schema and checkout notebook with four sections:
- Store schema: customers, products, inventory, orders, and order lines.
- Seed verification: row counts, inactive products, low-stock products, and one seed order total.
- Transactional checkout: reserve the last starter kit for customer 4 with visible rehearsal and verification.
- Final acceptance: prove the new order, line item, inventory movement, and final table counts.
Keep the store deliberately small. The point is not a full storefront. The point is a database design that refuses bad rows and a checkout transaction that tells one coherent story.
Workspace
Run the starter SQL first. Then replace or extend the TODO blocks with your own answers. The starter code creates the schema and seed data, then leaves the checkout scaffold commented so the rehearsal ritual stays visible.
Checkpoint 1: design the schema
Build the five tables and inspect the constraints before you trust the seed.
- Create
customers,products,inventory,orders, andorder_lines. - Give every table a primary key.
- Add unique rules for customer email and product SKU.
- Add foreign keys from inventory to products, orders to customers, and order lines to orders/products.
- Add
CHECKconstraints for nonnegative money, positive quantities, nonnegative inventory, and allowed order statuses.
Acceptance criteria:
- The database has exactly 5 user tables with these names:
customers,inventory,order_lines,orders, andproducts. orders.statusaccepts onlyreserved,paid, orcanceled.order_lines.quantitymust be greater than 0.- Inventory values cannot be negative.
Checkpoint 2: trust the seed
Seed the store with deterministic rows and prove the data shape.
Acceptance criteria:
- Seed counts are
customers4,inventory5,order_lines4,orders3, andproducts5. - There is 1 inactive product:
gift-card. - There is 1 active low-stock item with available stock at or below 1:
starter-kit, with available 1 and reserved 0. - Seed order 1002 has 2 line rows and a computed total of 47.00.
Checkpoint 3: run the transactional checkout
Create order 1004 for customer 4 on 2026-08-04. It buys 1 starter-kit at 18.00.
The safe shape is:
- Rehearse the customer, product, and inventory row.
BEGIN.SELECT ... FOR UPDATEthestarter-kitinventory row.- Run a guarded
UPDATEthat moves inventory from available to reserved only when enough stock remains. - Insert the order and order line.
- Verify the inventory row and the order line.
COMMIT.
Acceptance criteria:
- Order 1004 belongs to customer 4, has status
reserved, date2026-08-04, and total 18.00. - Order 1004 has line 1 for product
starter-kit, quantity 1, unit price 18.00, and computed line total 18.00. - Inventory for
starter-kitmoves from available 1 and reserved 0 to available 0 and reserved 1.
Checkpoint 4: final acceptance
After the checkout commits, prove the final state.
Acceptance criteria:
- Total orders are 4.
- Total order lines are 5.
starter-kithas available 0 and reserved 1.- There is still exactly 1 inactive product.
Stretch goals
Try these after the required checkpoints pass:
- Add a
paymentstable that records order 1004's payment on2026-08-04. - Add a
shipmentstable and a constraint that only allowsqueued,packed, orshipped. - Create a view that shows each order with its computed line total and compare it with
orders.total_amount. - Add an out-of-stock checkout attempt for
starter-kitafter order 1004: rehearse the inventory row first, then prove the guardedUPDATEaffects 0 rows.
How to get unstuck
Use Section 9 when a table definition feels mushy: name the fact, pick the smallest honest type, then make NULL and defaults deliberate. Use 9.2 and 9.3 for constraints and keys. Use Section 10 when you are unsure whether a fact belongs on products, inventory, orders, or order_lines: read the grain of the row.
Use Section 11 only when you are tempted to hide behavior behind a view or trigger; this project should keep checkout explicit. Use 12.1 for transaction choreography, 12.2 for retry thinking, and 12.3 for SELECT ... FOR UPDATE and lock-order discipline. Keep every write boring enough to review.