DynamoDB Single Table Design


In Part 1, we looked at the basics of DynamoDB, including what it is, how it stores data, and how it differs from traditional relational databases. We also covered concepts like partition keys, sort keys, queries, scans, and indexes. Now, we will design a single-table schema that uses these ideas to create a real-world serverless application: a medical test booking system.
The key principle of single-table design is to organize your table around access patterns instead of entities. This requires shifting from relational normalization to denormalization, allowing different entity types to exist in one table. In the cloud, storage is cheap compared to compute and query costs, making this method efficient and cost-effective.
The medical test booking system involves multiple entities and relationships. Each patient can book several tests. Each test is linked to a test type template, and doctors provide results with descriptions and explanations. In a relational model, you would typically have separate tables for patients, bookings, tests, test types, and test results. In DynamoDB, we combine these entities into a single table and organize items by access patterns.
Before we design the table, we will define the key access patterns:
Designing the table around these queries ensures that each operation is efficient by using queries or indexes instead of costly scans.
The essence of single-table design is choosing the right partition key (PK) and sort key (SK) for each item type. We will also use GSIs to support alternative access patterns.
Here’s how we can structure our MedicalTests table:
We will store the following item types in the same table:
We will use a composite PK/SK design:
Example Key Patterns
Patients
PK = PATIENT#<PatientId>
SK = PROFILE
This item stores a patient’s basic information:
| PK | SK | Attributes |
|---|---|---|
| PATIENT#123 | PROFILE | Name: John Doe, DOB: 1990-01-01 |
Bookings for a Patient
PK = PATIENT#<PatientId>
SK = BOOKING#<BookingId>
Each booking is a separate item:
| PK | SK | Attributes |
|---|---|---|
| PATIENT#123 | BOOKING#001 | BookingDate: 2025-12-01 |
| PATIENT#123 | BOOKING#002 | BookingDate: 2025-12-15 |
Tests within a Booking
PK = PATIENT#<PatientId>
SK = BOOKING#<BookingId>#TEST#<TestId>
Each test is an item under the patient partition:
| PK | SK | Attributes |
|---|---|---|
| PATIENT#123 | BOOKING#001#TEST#A1 | TestType: BloodTest, Status: Pending |
| PATIENT#123 | BOOKING#001#TEST#A2 | TestType: XRay, Status: Pending |
Test Results
PK = PATIENT#<PatientId>
SK = BOOKING#<BookingId>#TEST#<TestId>#RESULT
Doctors record results here:
| PK | SK | Attributes |
|---|---|---|
| PATIENT#123 | BOOKING#001#TEST#A1#RESULT | Result: Normal, Notes: Healthy blood levels |
Test Type Reference Data
PK = TESTTYPE#<TypeId>
SK = METADATA
Stores template info for each test:
| PK | SK | Attributes |
|---|---|---|
| TESTTYPE#BloodTest | METADATA | Name: Blood Test, Description: Measures blood components |
To support queries beyond the primary key, we define GSIs.
GSI1: Retrieve all tests in a booking
GSI1PK = BOOKING#<BookingId>
GSI1SK = TEST#<TestId>
GSI2: Retrieve all tests of a specific test type
GSI2PK = TESTTYPE#<TypeId>
GSI2SK = TEST#<TestId>
GSI3: Retrieve unreviewed tests
GSI3PK = TESTSTATUS#PENDING_REVIEW
GSI3SK = PATIENT#<PatientId>#BOOKING#<BookingId>#TEST#<TestId>
These indexes allow efficient queries that would otherwise require scans.
Retrieving data is simple. To get all bookings for a patient, query using PK = PATIENT#123 and SK beginning with BOOKING#. To get all tests in a booking, query GSI1 using BOOKING#001 as the partition key. Doctors can easily find all pending tests using GSI3, bypassing costly scans across the table.
Single-table design in DynamoDB reduces operational complexity, ensures predictable performance, and lowers read and write costs by combining related items into the same partition. By organizing your table around access patterns, you eliminate the need for joins, avoid expensive scan operations, and keep queries fast and efficient as data grows.
The Medical Test Booking System shows how different entity types—patients, bookings, tests, results, and test type templates—can coexist in one table while supporting all the needed queries through careful PK/SK design and well-planned GSIs.
Single-table design may seem strange at first, especially for those familiar with relational databases. However, once you adopt access pattern-driven design, DynamoDB becomes a strong, scalable foundation for serverless applications. Developers gain both speed and flexibility while ensuring operational and cost efficiency stays in focus.