

# Best practices for modeling relational data in DynamoDB
Relational modeling

This section provides best practices for modeling relational data in Amazon DynamoDB. First, we introduce traditional data modeling concepts. Then, we describe the advantages of using DynamoDB over traditional relational database management systems—how it eliminates the need for JOIN operations and reduces overhead. 

We then explain how to design a DynamoDB table that scales efficiently. Finally, we provide an example of how to model relational data in DynamoDB.

**Topics**
+ [

## Traditional relational database models
](#SQLtoNoSQL.relational-modeling2)
+ [

## How DynamoDB eliminates the need for JOIN operations
](#bp-relational-modeling-joins)
+ [

## How DynamoDB transactions eliminate overhead to the write process
](#bp-relational-modeling-transactions)
+ [

# First steps for modeling relational data in DynamoDB
](bp-modeling-nosql.md)
+ [

# Example of modeling relational data in DynamoDB
](bp-modeling-nosql-B.md)

## Traditional relational database models
Traditional relational database models

A traditional relational database management system (RDBMS) stores data in a normalized relational structure. The objective of the relational data model is to reduce the duplication of data (through normalization) to support referential integrity and reduce data anomalies. 

The following schema is an example of a relational data model for a generic order-entry application. The application supports a human resources schema that backs the operational and business support systems of a theoretical manufacturer.

![\[Example RDBMS schema.\]](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/images/RDBMS.png)


As a non-relational database service, DynamoDB offers many advantages over traditional relational database management systems. 

## How DynamoDB eliminates the need for JOIN operations


An RDBMS uses a structure query language (SQL) to return data to the application. Because of the normalization of the data model, such queries typically require the use of the `JOIN` operator to combine data from one or more tables.

For example, to generate a list of purchase order items sorted by the quantity in stock at all warehouses that can ship each item, you could issue the following SQL query against the preceding schema.

```
SELECT * FROM Orders
  INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
  INNER JOIN Products ON Products.Product_ID = Order_Items.Product_ID
  INNER JOIN Inventories ON Products.Product_ID = Inventories.Product_ID
  ORDER BY Quantity_on_Hand DESC
```

SQL queries of this kind can provide a flexible API for accessing data, but they require a significant amount of processing. Each join in the query increases the runtime complexity of the query because the data for each table must stage and then be assembled to return the result set. 

Additional factors that can impact how long it takes the queries to run are the size of the tables and whether the columns being joined have indexes. The preceding query initiates complex queries across several tables and then sorts the result set.

Eliminating the need for `JOINs` is at the heart of NoSQL data modeling. This is why we built DynamoDB to support Amazon.com, and why DynamoDB can deliver consistent performance at any scale. Given the runtime complexity of SQL queries and `JOINs`, RDBMS performance is not constant at scale. This causes performance issues as customer applications grow.

While normalizing data does reduce the amount of data stored to disk, often the most constrained resources that impact performance are CPU time and network latency. 

DynamoDB is built to minimize both constraints by eliminating `JOINs` (and encouraging denormalization of data) and optimizing the database architecture to fully answer an application query with a single request to an item. These qualities enable DynamoDB to provide single-digit, millisecond performance at any scale. This is because the runtime complexity for DynamoDB operations is constant, regardless of data size, for common access patterns.

## How DynamoDB transactions eliminate overhead to the write process


Another factor that can slow down an RDBMS is the use of transactions to write to a normalized schema. As shown in the example, relational data structures used by most online transaction processing (OLTP) applications must be broken down and distributed across multiple logical tables when they are stored in an RDBMS. 

Therefore, an ACID-compliant transaction framework is necessary to avoid race conditions and data integrity issues that could occur if an application tries to read an object that is in the process of being written. Such a transaction framework, when coupled with a relational schema, can add significant overhead to the write process.

The implementation of transactions in DynamoDB prohibits common scaling issues that are found with an RDBMS. DynamoDB does this by issuing a transaction as a single API call and bounding the number of items that can be accessed in that single transaction. Long-running transactions can cause operational issues by holding locks on the data either for a long time, or perpetually, because the transaction is never closed. 

To prevent such issues in DynamoDB, transactions were implemented with two distinct API operations: `TransactWriteItems` and `TransactGetItems`. These API operations do not have begin and end semantics that are common in an RDBMS. Further, DynamoDB has a 100-item access limit within a transaction to similarly prevent long-running transactions. To learn more about DynamoDB transactions, see [Working with transactions](transactions.md).

For these reasons, when your business requires a low-latency response to high-traffic queries, taking advantage of a NoSQL system generally makes technical and economic sense. Amazon DynamoDB helps solve the problems that limit relational system scalability by avoiding them.

The performance of an RDBMS does not typically scale well for the following reasons:
+ It uses expensive joins to reassemble required views of query results.
+ It normalizes data and stores it on multiple tables that require multiple queries to write to disk.
+ It generally incurs the performance costs of an ACID-compliant transaction system.

DynamoDB scales well for these reasons:
+ Schema flexibility lets DynamoDB store complex hierarchical data within a single item.
+ Composite key design lets it store related items close together on the same table.
+ Transactions are performed in a single operation. The limit for the number of items that can be accessed is 100, to avoid long-running operations.

Queries against the data store become much simpler, often in the following form:

```
SELECT * FROM Table_X WHERE Attribute_Y = "somevalue"
```

DynamoDB does far less work to return the requested data compared to the RDBMS in the earlier example.

# First steps for modeling relational data in DynamoDB
First steps

**Note**  
NoSQL design requires a different mindset than RDBMS design. For an RDBMS, you can create a normalized data model without thinking about access patterns. You can then extend it later when new questions and query requirements arise. By contrast, in Amazon DynamoDB, you shouldn't start designing your schema until you know the questions that it needs to answer. Understanding the business problems and the application use cases up front is absolutely essential.

To start designing a DynamoDB table that will scale efficiently, you must take several steps first to identify the access patterns that are required by the operations and business support systems (OSS/BSS) that it needs to support:
+ For new applications, review user stories about activities and objectives. Document the various use cases you identify, and analyze the access patterns that they require.
+ For existing applications, analyze query logs to find out how people are currently using the system and what the key access patterns are.

After completing this process, you should end up with a list that might look something like the following.


**Access Patterns for Order Entry Application**  

| Pattern \$1 | Access Pattern Description | 
| --- | --- | 
| 1 | Look up Employee Details by Employee ID | 
| 2 | Query Employee Details by Employee Name | 
| 3 | Find an Employee's Phone Number(s) | 
| 4 | Find a Customer's Phone Number(s) | 
| 5 | Get Orders for Customer within Date Range | 
| 6 | Show all Open Orders within Date Range | 
| 7 | See all Employees hired recently | 
| 8 | Find all Employees in Warehouse | 
| 9 | Get all Items on Order for Product | 
| 10 | Get Inventories for Product at all Warehouses | 
| 11 | Get Customers by Account Rep | 
| 12 | Get Orders by Account Rep | 
| 13 | Get Employees with Job Title | 
| 14 | Get Inventory by Product and Warehouse | 
| 15 | Get Total Product Inventory | 

In a real application, your list might be much longer. But this collection represents the range of query pattern complexity that you might find in a production environment.

A modern approach to DynamoDB schema design uses aggregate-oriented principles, grouping data based on access patterns rather than rigid entity boundaries. This approach considers multiple design patterns:
+ *Single Table Design* - Using composite sort keys, overloaded global secondary indexes, and adjacency list patterns to store multiple entity types in one table
+ *Multi-Table Design* - Using separate tables for entities with independent operational characteristics and low access correlation, with strategic GSIs for cross-entity queries
+ *Aggregate Design* - Embedding related data when always accessed together (Order \$1 OrderItems) or using item collections for identifying relationships (Product \$1 Inventory)

The choice between these approaches depends on your specific access patterns, data characteristics, and operational requirements. You can use these elements to structure the data so that an application can retrieve whatever it needs for a given access pattern using a single query on a table or index.

**Note**  
The choice between single-table and multi-table design depends on your specific requirements. Single-table design works well when entities have high access correlation and similar operational characteristics. Multi-table design is preferred when entities have independent operational requirements, different access patterns, or when you need clear operational boundaries. The example in this guide demonstrates a multi-table approach with strategic aggregation and denormalization.

To use NoSQL Workbench for DynamoDB to help visualize your partition key design, see [Building data models with NoSQL Workbench](workbench.Modeler.md).

# Example of modeling relational data in DynamoDB
Example

This example describes how to model relational data in Amazon DynamoDB. The DynamoDB table design corresponds to the relational order entry schema that is shown in [Relational modeling](bp-relational-modeling.md). This design uses multiple specialized tables rather than a single adjacency list, providing clear operational boundaries while leveraging strategic GSIs to serve all access patterns efficiently.

The design approach uses aggregate-oriented principles, grouping data based on access patterns rather than rigid entity boundaries. Key design decisions include using separate tables for entities with low access correlation, embedding related data when always accessed together, and using item collections for identifying relationships.

The following tables and their accompanying indexes support the relational order entry schema:

## Employee Table Design


The Employee table stores employee information as a single entity per item, optimized for direct employee lookups and supporting multiple query patterns through strategic GSIs. This table demonstrates the principle of designing separate tables for entities with independent operational characteristics and low cross-entity access correlation.

The table uses a simple partition key (employee\$1id) without a sort key, as each employee is a distinct entity. Four GSIs enable efficient querying by different attributes:
+ *EmployeeByName GSI* - Uses INCLUDE projection with all employee attributes to support complete employee detail retrieval by name, handling potential duplicate names with employee\$1id as sort key
+ *EmployeeByWarehouse GSI* - Uses INCLUDE projection with only essential attributes (name, job\$1title, hire\$1date) to minimize storage costs while supporting warehouse-based queries
+ *EmployeeByJobTitle GSI* - Enables role-based queries with INCLUDE projection for reporting and organizational analysis
+ *EmployeeByHireDate GSI* - Uses a static partition key value "EMPLOYEE" with hire\$1date as sort key to enable efficient date range queries for recent hires. Since employee additions/updates are typically under 1,000 WCU, a single partition can handle the write load without hot partition issues


**Employee Table - Base Table Structure**  

| employee\$1id (PK) | name | phone\$1numbers | warehouse\$1id | job\$1title | hire\$1date | entity\$1type | 
| --- | --- | --- | --- | --- | --- | --- | 
| emp\$1001 | John Smith | ["\$11-555-0101"] | wh\$1sea | Manager | 2024-03-15 | EMPLOYEE | 
| emp\$1002 | Jane Doe | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | Associate | 2025-01-10 | EMPLOYEE | 
| emp\$1003 | Bob Wilson | ["\$11-555-0104"] | wh\$1pdx | Associate | 2025-06-20 | EMPLOYEE | 
| emp\$1004 | Alice Brown | ["\$11-555-0105"] | wh\$1pdx | Supervisor | 2023-11-05 | EMPLOYEE | 
| emp\$1005 | Charlie Davis | ["\$11-555-0106"] | wh\$1sea | Associate | 2025-12-01 | EMPLOYEE | 


**EmployeeByName GSI - Supporting Employee Name Queries**  

| name (GSI-PK) | employee\$1id (GSI-SK) | phone\$1numbers | warehouse\$1id | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | --- | 
| Alice Brown | emp\$1004 | ["\$11-555-0105"] | wh\$1pdx | Supervisor | 2023-11-05 | 
| Bob Wilson | emp\$1003 | ["\$11-555-0104"] | wh\$1pdx | Associate | 2025-06-20 | 
| Charlie Davis | emp\$1005 | ["\$11-555-0106"] | wh\$1sea | Associate | 2025-12-01 | 
| Jane Doe | emp\$1002 | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | Associate | 2025-01-10 | 
| John Smith | emp\$1001 | ["\$11-555-0101"] | wh\$1sea | Manager | 2024-03-15 | 


**EmployeeByWarehouse GSI - Supporting Warehouse Queries**  

| warehouse\$1id (GSI-PK) | employee\$1id (GSI-SK) | name | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | 
| wh\$1pdx | emp\$1003 | Bob Wilson | Associate | 2025-06-20 | 
| wh\$1pdx | emp\$1004 | Alice Brown | Supervisor | 2023-11-05 | 
| wh\$1sea | emp\$1001 | John Smith | Manager | 2024-03-15 | 
| wh\$1sea | emp\$1002 | Jane Doe | Associate | 2025-01-10 | 
| wh\$1sea | emp\$1005 | Charlie Davis | Associate | 2025-12-01 | 


**EmployeeByJobTitle GSI - Supporting Job Title Queries**  

| job\$1title (GSI-PK) | employee\$1id (GSI-SK) | name | warehouse\$1id | hire\$1date | 
| --- | --- | --- | --- | --- | 
| Associate | emp\$1002 | Jane Doe | wh\$1sea | 2025-01-10 | 
| Associate | emp\$1003 | Bob Wilson | wh\$1pdx | 2025-06-20 | 
| Associate | emp\$1005 | Charlie Davis | wh\$1sea | 2025-12-01 | 
| Manager | emp\$1001 | John Smith | wh\$1sea | 2024-03-15 | 
| Supervisor | emp\$1004 | Alice Brown | wh\$1pdx | 2023-11-05 | 


**EmployeeByHireDate GSI - Supporting Recent Hire Queries**  

| entity\$1type (GSI-PK) | hire\$1date (GSI-SK) | employee\$1id | name | warehouse\$1id | 
| --- | --- | --- | --- | --- | 
| EMPLOYEE | 2023-11-05 | emp\$1004 | Alice Brown | wh\$1pdx | 
| EMPLOYEE | 2024-03-15 | emp\$1001 | John Smith | wh\$1sea | 
| EMPLOYEE | 2025-01-10 | emp\$1002 | Jane Doe | wh\$1sea | 
| EMPLOYEE | 2025-06-20 | emp\$1003 | Bob Wilson | wh\$1pdx | 
| EMPLOYEE | 2025-12-01 | emp\$1005 | Charlie Davis | wh\$1sea | 

## Customer Table Design


The Customer table maintains customer information with strategic denormalization of account\$1rep\$1id to enable efficient account representative queries. This design choice trades slight storage overhead for query performance, eliminating the need for joins between customer and account representative data.

The table supports multiple phone numbers per customer using a list attribute, demonstrating DynamoDB's schema flexibility. The single GSI enables account representative workflows:
+ *CustomerByAccountRep GSI* - Uses INCLUDE projection with name and email attributes to support account rep customer management without requiring full customer record retrieval


**Customer Table - Base Table Structure**  

| customer\$1id (PK) | name | phone\$1numbers | email | account\$1rep\$1id | 
| --- | --- | --- | --- | --- | 
| cust\$1001 | Acme Corp | ["\$11-555-1001"] | contact@acme.com | rep\$1001 | 
| cust\$1002 | TechStart Inc | ["\$11-555-1002", "\$11-555-1003"] | info@techstart.com | rep\$1001 | 
| cust\$1003 | Global Traders | ["\$11-555-1004"] | sales@globaltraders.com | rep\$1002 | 
| cust\$1004 | BuildRight LLC | ["\$11-555-1005"] | orders@buildright.com | rep\$1002 | 
| cust\$1005 | FastShip Co | ["\$11-555-1006"] | support@fastship.com | rep\$1003 | 


**CustomerByAccountRep GSI - Supporting Account Rep Queries**  

| account\$1rep\$1id (GSI-PK) | customer\$1id (GSI-SK) | name | email | 
| --- | --- | --- | --- | 
| rep\$1001 | cust\$1001 | Acme Corp | contact@acme.com | 
| rep\$1001 | cust\$1002 | TechStart Inc | info@techstart.com | 
| rep\$1002 | cust\$1003 | Global Traders | sales@globaltraders.com | 
| rep\$1002 | cust\$1004 | BuildRight LLC | orders@buildright.com | 
| rep\$1003 | cust\$1005 | FastShip Co | support@fastship.com | 

## Order Table Design


The Order table uses vertical partitioning with separate items for order headers and order items. This design enables efficient product-based queries while maintaining all order components within the same partition for efficient access. Each order consists of multiple items:
+ *Order Header* - Contains order metadata with PK=order\$1id, SK=order\$1id
+ *Order Items* - Individual line items with PK=order\$1id, SK=product\$1id, enabling direct product queries

**Note**  
This vertical partitioning approach trades the simplicity of embedded order items for enhanced query flexibility. Each order item becomes a separate DynamoDB item, enabling efficient product-based queries while maintaining all order data within the same partition for efficient retrieval in a single request.

The table includes strategic denormalization of account\$1rep\$1id (duplicated from Customer table) to enable direct account representative queries without requiring customer lookups. For high-throughput write scenarios, OPEN orders include status and shard attributes to enable write sharding across multiple partitions.

Four GSIs support different query patterns with optimized projections:
+ *OrderByCustomerDate GSI* - Uses INCLUDE projection with order summary and item details to support customer order history with date range filtering
+ *OpenOrdersByDate GSI (Sparse, Sharded)* - Uses multi-attribute partition key (status \$1 shard) with 5 shards to distribute 5,000 WPS (writes per second) across partitions (1,000 WPS each, matching DynamoDB's 1,000 WCU per partition limit). Only indexes OPEN orders (20% of total), which can help reduce GSI storage costs. Requires parallel queries across all 5 shards with client-side result merging
+ *OrderByAccountRep GSI* - Uses INCLUDE projection with order summary attributes to support account representative workflows without full order details
+ *ProductInOrders GSI* - Created from OrderItem records (PK=order\$1id, SK=product\$1id), this GSI enables queries to find all orders containing a specific product. Uses INCLUDE projection with order context (customer\$1id, order\$1date, quantity) for product demand analysis


**Order Table - Base Table Structure (Vertical Partitioning)**  

| PK | SK | customer\$1id | order\$1date | status | account\$1rep\$1id | quantity | price | shard | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| ord\$1001 | ord\$1001 | cust\$1001 | 2025-11-15 | CLOSED | rep\$1001 |  |  |  | 
| ord\$1001 | prod\$1100 |  |  |  |  | 5 | 25.00 |  | 
| ord\$1002 | ord\$1002 | cust\$1001 | 2025-12-20 | OPEN | rep\$1001 |  |  | 0 | 
| ord\$1002 | prod\$1101 |  |  |  |  | 10 | 15.00 |  | 
| ord\$1003 | ord\$1003 | cust\$1002 | 2026-01-05 | OPEN | rep\$1001 |  |  | 2 | 
| ord\$1003 | prod\$1100 |  |  |  |  | 3 | 25.00 |  | 


**OrderByCustomerDate GSI - Supporting Customer Order Queries**  

| customer\$1id (GSI-PK) | order\$1date (GSI-SK) | order\$1id | status | total\$1amount | order\$1items | shard | 
| --- | --- | --- | --- | --- | --- | --- | 
| cust\$1001 | 2025-11-15 | ord\$1001 | CLOSED | 225.00 | [\$1product\$1id: "prod\$1100", qty: 5\$1] |  | 
| cust\$1001 | 2025-12-20 | ord\$1002 | OPEN | 150.00 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 0 | 
| cust\$1002 | 2026-01-05 | ord\$1003 | OPEN | 175.00 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 2 | 
| cust\$1003 | 2025-10-10 | ord\$1004 | CLOSED | 250.00 | [\$1product\$1id: "prod\$1101", qty: 5\$1] |  | 
| cust\$1004 | 2026-01-03 | ord\$1005 | OPEN | 200.00 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 1 | 


**OpenOrdersByDate GSI (Sparse, Sharded) - Supporting High-Throughput Open Order Queries**  

| status (GSI-PK-1) | shard (GSI-PK-2) | order\$1date (SK) | order\$1id | customer\$1id | account\$1rep\$1id | order\$1items | total\$1amount | 
| --- | --- | --- | --- | --- | --- | --- | --- | 
| OPEN | 0 | 2025-12-20 | ord\$1002 | cust\$1001 | rep\$1001 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 150.00 | 
| OPEN | 1 | 2026-01-03 | ord\$1005 | cust\$1004 | rep\$1002 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 200.00 | 
| OPEN | 2 | 2026-01-05 | ord\$1003 | cust\$1002 | rep\$1001 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 175.00 | 


**OrderByAccountRep GSI - Supporting Account Rep Order Queries**  

| account\$1rep\$1id (GSI-PK) | order\$1date (GSI-SK) | order\$1id | customer\$1id | status | total\$1amount | 
| --- | --- | --- | --- | --- | --- | 
| rep\$1001 | 2025-11-15 | ord\$1001 | cust\$1001 | CLOSED | 225.00 | 
| rep\$1001 | 2025-12-20 | ord\$1002 | cust\$1001 | OPEN | 150.00 | 
| rep\$1001 | 2026-01-05 | ord\$1003 | cust\$1002 | OPEN | 175.00 | 
| rep\$1002 | 2025-10-10 | ord\$1004 | cust\$1003 | CLOSED | 250.00 | 
| rep\$1002 | 2026-01-03 | ord\$1005 | cust\$1004 | OPEN | 200.00 | 


**ProductInOrders GSI - Supporting Product Order Queries**  

| product\$1id (GSI-PK) | order\$1id (GSI-SK) | customer\$1id | order\$1date | quantity | 
| --- | --- | --- | --- | --- | 
| prod\$1100 | ord\$1001 | cust\$1001 | 2025-11-15 | 5 | 
| prod\$1100 | ord\$1003 | cust\$1002 | 2026-01-05 | 3 | 
| prod\$1101 | ord\$1002 | cust\$1001 | 2025-12-20 | 10 | 

## Product Table Design


The Product table uses the item collection pattern to store both product metadata and inventory data within the same partition. This design leverages the identifying relationship between products and inventory - inventory cannot exist without a parent product. Using PK=product\$1id with SK=product\$1id for product metadata and SK=warehouse\$1id for inventory items eliminates the need for a separate Inventory table and GSI, reducing costs by approximately 50%.

This pattern enables efficient queries for both individual warehouse inventory (GetItem with composite key) and all warehouse inventory for a product (Query on partition key). The total\$1inventory attribute in the product metadata item provides denormalized aggregation for quick total inventory lookups.


**Product Table - Base Table Structure (Item Collection Pattern)**  

| product\$1id (PK) | warehouse\$1id (SK) | product\$1name | category | unit\$1price | inventory\$1quantity | total\$1inventory | 
| --- | --- | --- | --- | --- | --- | --- | 
| prod\$1100 | prod\$1100 | Widget A | Hardware | 25.00 |  | 500 | 
| prod\$1100 | wh\$1sea |  |  |  | 200 |  | 
| prod\$1100 | wh\$1pdx |  |  |  | 150 |  | 
| prod\$1100 | wh\$1atl |  |  |  | 150 |  | 
| prod\$1101 | prod\$1101 | Gadget B | Electronics | 50.00 |  | 300 | 
| prod\$1101 | wh\$1sea |  |  |  | 100 |  | 
| prod\$1101 | wh\$1pdx |  |  |  | 200 |  | 

Each table is designed with specific Global Secondary Indexes (GSIs) to support the required access patterns efficiently. The design uses aggregate-oriented principles with strategic denormalization and sparse indexing to optimize both performance and cost.

Key design optimizations include:
+ *Sparse GSI* - OpenOrdersByDate only indexes OPEN orders (20% of total), which can help reduce GSI storage costs
+ *Item Collection Pattern* - Product table stores inventory using PK=product\$1id, SK=warehouse\$1id to eliminate separate inventory table
+ *Order \$1 OrderItems Aggregation* - Embedded as single item due to 100% access correlation
+ *Strategic Denormalization* - account\$1rep\$1id duplicated in Order table for efficient queries

Finally, you can revisit the access patterns that were defined earlier. The following table shows how each access pattern is efficiently supported using the multi-table design with strategic GSIs. Each pattern uses either direct key lookups or single GSI queries, avoiding expensive scans and providing consistent performance at any scale.


| S. No. | Access patterns | Query conditions | 
| --- | --- | --- | 
|  1  |  Look up Employee Details by Employee ID  |  Employee Table: GetItem(employee\$1id="emp\$1001")  | 
|  2  |  Query Employee Details by Employee Name  |  EmployeeByName GSI: Query(name="John Smith")  | 
|  3  |  Find an Employee's Phone Number(s)  |  Employee Table: GetItem(employee\$1id="emp\$1001")  | 
|  4  |  Find a Customer's Phone Number(s)  |  Customer Table: GetItem(customer\$1id="cust\$1001")  | 
|  5  |  Get Orders for Customer within Date Range  |  OrderByCustomerDate GSI: Query(customer\$1id="cust\$1001", order\$1date BETWEEN "2025-01-01" AND "2025-12-31")  | 
|  6  |  Show all Open Orders within Date Range  |  OpenOrdersByDate GSI: Query 5 shards in parallel with multi-attribute PK (status="OPEN" \$1 shard=0-4), SK=order\$1date BETWEEN "2025-01-01" AND "2025-12-31", merge results  | 
|  7  |  See all Employees hired recently  |  EmployeeByHireDate GSI: Query(entity\$1type="EMPLOYEE", hire\$1date >= "2025-01-01")  | 
|  8  |  Find all Employees in Warehouse  |  EmployeeByWarehouse GSI: Query(warehouse\$1id="wh\$1sea")  | 
|  9  |  Get all Items on Order for Product  |  ProductInOrders GSI: Query(product\$1id="prod\$1100")  | 
|  10  |  Get Inventories for Product at all Warehouses  |  Product Table: Query(product\$1id="prod\$1100")  | 
|  11  |  Get Customers by Account Rep  |  CustomerByAccountRep GSI: Query(account\$1rep\$1id="rep\$1001")  | 
|  12  |  Get Orders by Account Rep  |  OrderByAccountRep GSI: Query(account\$1rep\$1id="rep\$1001")  | 
|  13  |  Get Employees with Job Title  |  EmployeeByJobTitle GSI: Query(job\$1title="Manager")  | 
|  14  |  Get Inventory by Product and Warehouse  |  Product Table: GetItem(product\$1id="prod\$1100", warehouse\$1id="wh\$1sea")  | 
|  15  |  Get Total Product Inventory  |  Product Table: GetItem(product\$1id="prod\$1100", warehouse\$1id="prod\$1100")  | 