

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 在 DynamoDB 中打造關聯式資料模型的範例
<a name="bp-modeling-nosql-B"></a>

此範例說明如何在 Amazon DynamoDB 中打造關聯式資料模型。DynamoDB 資料表設計對應至顯示在 中的關聯式順序項目結構描述[關聯式模型](bp-relational-modeling.md)。此設計使用多個專用資料表，而不是單一相鄰清單，提供清晰的操作界限，同時利用策略 GSIs 有效地為所有存取模式提供服務。

設計方法使用彙總導向原則，根據存取模式而非剛性實體邊界來分組資料。關鍵設計決策包括針對具有低存取關聯性的實體使用個別資料表、一律同時存取時內嵌相關資料，以及使用項目集合來識別關係。

下表及其隨附的索引支援關聯式順序項目結構描述：

## 員工資料表設計
<a name="employee-table-design"></a>

員工資料表會將員工資訊儲存為每個項目的單一實體，針對直接員工查詢進行最佳化，並透過策略 GSIs 支援多個查詢模式。此資料表示範為具有獨立操作特性和低跨實體存取相關性的實體設計個別資料表的原則。

資料表使用不含排序索引鍵的簡單分割區索引鍵 (employee\_id)，因為每個員工都是不同的實體。四個 GSIs 可讓不同屬性有效率地查詢：
+ *EmployeeByName GSI* - 將 INCLUDE 投影與所有員工屬性搭配使用，以支援依名稱的完整員工詳細資訊擷取，並以 employee\_id 作為排序索引鍵處理潛在的重複名稱
+ *EmployeeByWarehouse GSI* - 僅將 INCLUDE 投影與基本屬性 (name， job\_title， hire\_date) 搭配使用，將儲存成本降至最低，同時支援以倉儲為基礎的查詢
+ *EmployeeByJobTitle GSI* - 使用 INCLUDE 投影啟用角色型查詢以進行報告和組織分析
+ *EmployeeByHireDate GSI* - 使用靜態分割區索引鍵值 "EMPLOYEE" 搭配 hire\_date 作為排序索引鍵，以啟用最近招聘的高效日期範圍查詢。由於員工新增/更新通常低於 1，000 WCU，因此單一分割區可以處理寫入負載，而不會發生熱分割區問題


**員工資料表 - 基礎資料表結構**  

| employee\_id (PK) | name | phone\_numbers | 倉儲 ID | job\_title | hire\_date | entity\_type | 
| --- | --- | --- | --- | --- | --- | --- | 
| emp\_001 | John Smith | 【"\+1-555-0101"】 | wh\_sea | 管理員 | 2024-03-15 | 員工 | 
| emp\_002 | Jane Doe | 【"\+1-555-0102"、"\+1-555-0103"】 | wh\_sea | 關聯 | 2025-01-10 | 員工 | 
| emp\_003 | Bob Wilson | 【"\+1-555-0104"】 | wh\_pdx | 關聯 | 2025-06-20 | 員工 | 
| emp\_004 | Alice 棕色 | 【"\+1-555-0105"】 | wh\_pdx | 主管 | 2023-11-05 | 員工 | 
| emp\_005 | Charlie Davis | 【"\+1-555-0106"】 | wh\_sea | 關聯 | 2025-12-01 | 員工 | 


**EmployeeByName GSI - 支援員工名稱查詢**  

| 名稱 (GSI-PK) | employee\_id (GSI-SK) | phone\_numbers | 倉儲 ID | job\_title | hire\_date | 
| --- | --- | --- | --- | --- | --- | 
| Alice 棕色 | emp\_004 | 【"\+1-555-0105"】 | wh\_pdx | 主管 | 2023-11-05 | 
| Bob Wilson | emp\_003 | 【"\+1-555-0104"】 | wh\_pdx | 關聯 | 2025-06-20 | 
| Charlie Davis | emp\_005 | 【"\+1-555-0106"】 | wh\_sea | 關聯 | 2025-12-01 | 
| Jane Doe | emp\_002 | 【"\+1-555-0102"、"\+1-555-0103"】 | wh\_sea | 關聯 | 2025-01-10 | 
| John Smith | emp\_001 | 【"\+1-555-0101"】 | wh\_sea | 管理員 | 2024-03-15 | 


**EmployeeByWarehouse GSI - 支援倉儲查詢**  

| warehouse\_id (GSI-PK) | employee\_id (GSI-SK) | name | job\_title | hire\_date | 
| --- | --- | --- | --- | --- | 
| wh\_pdx | emp\_003 | Bob Wilson | 關聯 | 2025-06-20 | 
| wh\_pdx | emp\_004 | Alice 棕色 | 主管 | 2023-11-05 | 
| wh\_sea | emp\_001 | John Smith | 管理員 | 2024-03-15 | 
| wh\_sea | emp\_002 | Jane Doe | 關聯 | 2025-01-10 | 
| wh\_sea | emp\_005 | Charlie Davis | 關聯 | 2025-12-01 | 


**EmployeeByJobTitle GSI - 支援任務標題查詢**  

| job\_title (GSI-PK) | employee\_id (GSI-SK) | name | 倉儲 ID | hire\_date | 
| --- | --- | --- | --- | --- | 
| 關聯 | emp\_002 | Jane Doe | wh\_sea | 2025-01-10 | 
| 關聯 | emp\_003 | Bob Wilson | wh\_pdx | 2025-06-20 | 
| 關聯 | emp\_005 | Charlie Davis | wh\_sea | 2025-12-01 | 
| 管理員 | emp\_001 | John Smith | wh\_sea | 2024-03-15 | 
| 主管 | emp\_004 | Alice 棕色 | wh\_pdx | 2023-11-05 | 


**EmployeeByHireDate GSI - 支援最近僱用查詢**  

| entity\_type (GSI-PK) | hire\_date (GSI-SK) | employee\_id | name | 倉儲 ID | 
| --- | --- | --- | --- | --- | 
| 員工 | 2023-11-05 | emp\_004 | Alice 棕色 | wh\_pdx | 
| 員工 | 2024-03-15 | emp\_001 | John Smith | wh\_sea | 
| 員工 | 2025-01-10 | emp\_002 | Jane Doe | wh\_sea | 
| 員工 | 2025-06-20 | emp\_003 | Bob Wilson | wh\_pdx | 
| 員工 | 2025-12-01 | emp\_005 | Charlie Davis | wh\_sea | 

## 客戶資料表設計
<a name="customer-table-design"></a>

客戶資料表使用 account\_rep\_id 的策略非正規化來維護客戶資訊，以啟用有效的帳戶代表查詢。此設計選擇會降低查詢效能的儲存體額外負荷，因此不需要在客戶和帳戶代表資料之間加入。

資料表支援每位客戶使用清單屬性的多個電話號碼，示範 DynamoDB 的結構描述彈性。單一 GSI 可啟用帳戶代表性工作流程：
+ *CustomerByAccountRep GSI* - 使用 INCLUDE 投影搭配名稱和電子郵件屬性，以支援帳戶代表客戶管理，而不需要完整的客戶記錄擷取


**客戶資料表 - 基礎資料表結構**  

| customer\_id (PK) | name | phone\_numbers | email | account\_rep\_id | 
| --- | --- | --- | --- | --- | 
| cust\_001 | Acme Corp | 【"\+1-555-1001"】 | contact@acme.com | rep\_001 | 
| cust\_002 | TechStart Inc | 【"\+1-555-1002"、"\+1-555-1003"】 | info@techstart.com | rep\_001 | 
| cust\_003 | 全球交易員 | 【"\+1-555-1004"】 | sales@globaltraders.com | rep\_002 | 
| cust\_004 | BuildRight LLC | 【"\+1-555-1005"】 | orders@buildright.com | rep\_002 | 
| cust\_005 | FastShip Co | 【"\+1-555-1006"】 | support@fastship.com | rep\_003 | 


**CustomerByAccountRep GSI - 支援帳戶代表查詢**  

| account\_rep\_id (GSI-PK) | customer\_id (GSI-SK) | name | email | 
| --- | --- | --- | --- | 
| rep\_001 | cust\_001 | Acme Corp | contact@acme.com | 
| rep\_001 | cust\_002 | TechStart Inc | info@techstart.com | 
| rep\_002 | cust\_003 | 全球交易員 | sales@globaltraders.com | 
| rep\_002 | cust\_004 | BuildRight LLC | orders@buildright.com | 
| rep\_003 | cust\_005 | FastShip Co | support@fastship.com | 

## 訂單資料表設計
<a name="order-table-design"></a>

訂單資料表針對訂單標頭和訂單項目使用具有個別項目的垂直分割。此設計可實現高效的產品型查詢，同時在同一分割區中維護所有訂單元件，以實現高效存取。每個訂單包含多個項目：
+ *訂單標頭* - 包含具有 PK=order\_id、SK=order\_id 的訂單中繼資料
+ *訂單項目* - PK=order\_id、SK=product\_id 的個別明細項目，啟用直接產品查詢

**注意**  
這種垂直分割方法會交換內嵌訂單項目的簡單性，以增強查詢彈性。每個訂單項目都會成為單獨的 DynamoDB 項目，可實現高效的產品型查詢，同時將所有訂單資料維持在同一個分割區中，以便在單一請求中高效擷取。

資料表包含 account\_rep\_id （與客戶資料表重複） 的策略非正規化，以啟用直接帳戶代表查詢，而不需要客戶查詢。對於高輸送量寫入案例，OPEN 訂單包含狀態和碎片屬性，以啟用跨多個分割區的寫入碎片。

四個 GSIs支援具有最佳化投影的不同查詢模式：
+ *OrderByCustomerDate GSI* - 使用 INCLUDE 投影搭配訂單摘要和項目詳細資訊，以支援具有日期範圍篩選的客戶訂單歷史記錄
+ *OpenOrdersByDate GSI （稀疏、碎片）* - 使用具有 5 個碎片的多屬性分割區索引鍵 （狀態 \+ 碎片），以跨分割區分配 5，000 WPS （每秒寫入數） （每個 WPS，每個分割區都符合 DynamoDB 的 1，000 WCU)。僅索引 OPEN 訂單 （總計 20%)，這有助於降低 GSI 儲存成本。需要跨所有 5 個碎片與用戶端結果合併的平行查詢
+ *OrderByAccountRep GSI* - 使用 INCLUDE 投影搭配訂單摘要屬性，以支援沒有完整訂單詳細資訊的帳戶代表工作流程
+ *ProductInOrders GSI* - 從 OrderItem 記錄 (PK=order\_id、SK=product\_id) 建立，此 GSI 可讓查詢尋找包含特定產品的所有訂單。使用 INCLUDE 投影搭配訂單內容 (customer\_id、order\_date、quity) 進行產品需求分析


**順序資料表 - 基礎資料表結構 （垂直分割）**  

| PK | SK | customer\_id | order\_date | status | account\_rep\_id | 數量 | 價格 | 碎片 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| ord\_001 | ord\_001 | cust\_001 | 2025-11-15 | CLOSED | rep\_001 |  |  |  | 
| ord\_001 | prod\_100 |  |  |  |  | 5 | 25.00 |  | 
| ord\_002 | ord\_002 | cust\_001 | 2025-12-20 | OPEN | rep\_001 |  |  | 0 | 
| ord\_002 | prod\_101 |  |  |  |  | 10 | 15.00 |  | 
| ord\_003 | ord\_003 | cust\_002 | 2026-01-05 | OPEN | rep\_001 |  |  | 2 | 
| ord\_003 | prod\_100 |  |  |  |  | 3 | 25.00 |  | 


**OrderByCustomerDate GSI - 支援客戶訂單查詢**  

| customer\_id (GSI-PK) | order\_date (GSI-SK) | order\_id | status | total\_amount | order\_items | 碎片 | 
| --- | --- | --- | --- | --- | --- | --- | 
| cust\_001 | 2025-11-15 | ord\_001 | CLOSED | 225.00 | 【{product\_id： "prod\_100"，數量：5}】 |  | 
| cust\_001 | 2025-12-20 | ord\_002 | OPEN | 150.00 | 【{product\_id： "prod\_101"，數量：10}】 | 0 | 
| cust\_002 | 2026-01-05 | ord\_003 | OPEN | 175.00 | 【{product\_id： "prod\_100"，數量：3}】 | 2 | 
| cust\_003 | 2025-10-10 | ord\_004 | CLOSED | 250.00 | 【{product\_id： "prod\_101"，數量：5}】 |  | 
| cust\_004 | 2026-01-03 | ord\_005 | OPEN | 200.00 | 【{product\_id： "prod\_100"，數量：20}】 | 1 | 


**OpenOrdersByDate GSI （稀疏、碎片） - 支援高輸送量的開放訂單查詢**  

| 狀態 (GSI-PK-1) | 碎片 (GSI-PK-2) | order\_date (SK) | order\_id | customer\_id | account\_rep\_id | order\_items | total\_amount | 
| --- | --- | --- | --- | --- | --- | --- | --- | 
| OPEN | 0 | 2025-12-20 | ord\_002 | cust\_001 | rep\_001 | 【{product\_id： "prod\_101"，數量：10}】 | 150.00 | 
| OPEN | 1 | 2026-01-03 | ord\_005 | cust\_004 | rep\_002 | 【{product\_id： "prod\_100"，數量：20}】 | 200.00 | 
| OPEN | 2 | 2026-01-05 | ord\_003 | cust\_002 | rep\_001 | 【{product\_id： "prod\_100"，數量：3}】 | 175.00 | 


**OrderByAccountRep GSI - 支援帳戶代表訂單查詢**  

| account\_rep\_id (GSI-PK) | order\_date (GSI-SK) | order\_id | customer\_id | status | total\_amount | 
| --- | --- | --- | --- | --- | --- | 
| rep\_001 | 2025-11-15 | ord\_001 | cust\_001 | CLOSED | 225.00 | 
| rep\_001 | 2025-12-20 | ord\_002 | cust\_001 | OPEN | 150.00 | 
| rep\_001 | 2026-01-05 | ord\_003 | cust\_002 | OPEN | 175.00 | 
| rep\_002 | 2025-10-10 | ord\_004 | cust\_003 | CLOSED | 250.00 | 
| rep\_002 | 2026-01-03 | ord\_005 | cust\_004 | OPEN | 200.00 | 


**ProductInOrders GSI - 支援產品訂單查詢**  

| product\_id (GSI-PK) | order\_id (GSI-SK) | customer\_id | order\_date | 數量 | 
| --- | --- | --- | --- | --- | 
| prod\_100 | ord\_001 | cust\_001 | 2025-11-15 | 5 | 
| prod\_100 | ord\_003 | cust\_002 | 2026-01-05 | 3 | 
| prod\_101 | ord\_002 | cust\_001 | 2025-12-20 | 10 | 

## 產品資料表設計
<a name="product-table-design"></a>

產品資料表使用項目集合模式，將產品中繼資料和庫存資料存放在相同的分割區中。此設計利用產品和庫存之間的識別關係 - 庫存不能在沒有父產品的情況下存在。使用 PK=product\_id 搭配 SK=product\_id 用於產品中繼資料和 SK=warehouse\_id 用於清查項目，不需要單獨的清查資料表和 GSI，將成本降低約 50%。

此模式可有效查詢個別倉儲庫存 (GetItem 搭配複合索引鍵） 和產品的所有倉儲庫存 （分割區索引鍵上的查詢）。產品中繼資料項目中的 total\_inventory 屬性提供非標準化彙總，以快速查詢總庫存。


**產品資料表 - 基礎資料表結構 （項目集合模式）**  

| product\_id (PK) | warehouse\_id (SK) | product\_name | category | unit\_price | inventory\_quantity | total\_inventory | 
| --- | --- | --- | --- | --- | --- | --- | 
| prod\_100 | prod\_100 | 小工具 A | 硬體 | 25.00 |  | 500 | 
| prod\_100 | wh\_sea |  |  |  | 200 |  | 
| prod\_100 | wh\_pdx |  |  |  | 150 |  | 
| prod\_100 | wh\_atl |  |  |  | 150 |  | 
| prod\_101 | prod\_101 | 小工具 B | 電子 | 50.00 |  | 300 | 
| prod\_101 | wh\_sea |  |  |  | 100 |  | 
| prod\_101 | wh\_pdx |  |  |  | 200 |  | 

每個資料表都設計有特定的全域次要索引 (GSIs)，可有效支援所需的存取模式。此設計使用彙總導向原則搭配策略去標準化和稀疏索引，以最佳化效能和成本。

主要設計最佳化包括：
+ *Sparse GSI* - OpenOrdersByDate 僅索引 OPEN 訂單 （總計 20%)，這有助於降低 GSI 儲存成本
+ *項目收集模式* - 產品資料表使用 PK=product\_id、SK=warehouse\_id 存放庫存，以消除單獨的庫存資料表
+ *Order \+ OrderItems 彙總* - 由於 100% 存取關聯而內嵌為單一項目
+ *策略非規範化* - 訂單表格中的 account\_rep\_id 重複，用於有效率的查詢

最後，您可以再次瀏覽先前定義的存取模式。下表顯示如何使用具有策略 GSIs多資料表設計，有效率地支援每個存取模式。每個模式都使用直接金鑰查詢或單一 GSI 查詢，避免昂貴的掃描，並在任何規模提供一致的效能。


| 序號 | 存取模式 | 查詢條件 | 
| --- | --- | --- | 
| 1 | 按員工 ID 查詢員工詳細資訊 | 員工資料表：GetItem(employee\_id="emp\_001") | 
| 2 | 按員工姓名查詢員工詳細資訊 | EmployeeByName GSI：Query(name="John Smith") | 
| 3 | 尋找員工的電話號碼 (s) | 員工資料表：GetItem(employee\_id="emp\_001") | 
| 4 | 尋找客戶的電話號碼 (s) | 客戶資料表：GetItem(customer\_id="cust\_001") | 
| 5 | 在日期範圍內為客戶取得訂單 | OrderByCustomerDate GSI： Query(customer\_id="cust\_001"， order\_date BETWEEN "2025-01-01" AND "2025-12-31") | 
| 6 | 顯示日期範圍內的所有開啟的訂單 | OpenOrdersByDate GSI：平行查詢 5 個碎片與多屬性 PK (status="OPEN" \+ shard=0-4)、SK=order\_date BETWEEN "2025-01-01" AND "2025-12-31"、合併結果 | 
| 7 | 查看所有最近僱用的員工 | EmployeeByHireDate GSI： Query(entity\_type="EMPLOYEE"， hire\_date >= "2025-01-01") | 
| 8 | 尋找 Warehouse 中的所有員工 | EmployeeByWarehouse GSI：Query(warehouse\_id="wh\_sea") | 
| 9 | 取得產品訂單上的所有項目 | ProductInOrders GSI：Query(product\_id="prod\_100") | 
| 10 | 在所有倉儲取得產品的庫存 | 產品資料表：Query(product\_id="prod\_100") | 
| 11 | 依帳戶代表取得客戶 | CustomerByAccountRep GSI：Query(account\_rep\_id="rep\_001") | 
| 12 | 依帳戶代表取得訂單 | OrderByAccountRep GSI：Query(account\_rep\_id="rep\_001") | 
| 13 | 取得具有任務標題的員工 | EmployeeByJobTitle GSI： Query(job\_title="Manager") | 
| 14 | 依產品和倉儲取得庫存 | 產品資料表：GetItem(product\_id="prod\_100"， warehouse\_id="wh\_sea") | 
| 15 | 取得產品庫存總計 | 產品資料表：GetItem(product\_id="prod\_100"， warehouse\_id="prod\_100") | 