

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

# 適用於 DynamoDB 資料表的資料建模
<a name="data-modeling"></a>

在我們深入探討資料建模之前，請務必了解一些 DynamoDB 基礎知識。DynamoDB 是一個使用鍵值的 NoSQL 資料庫，擁有靈活的結構描述。除了每個項目的索引鍵屬性之外，資料屬性集可以統整在一起，也可以各自獨立。DynamoDB 索引鍵結構描述的形式可以是簡單主索引鍵，其中分割區索引鍵可唯一識別項目，也可以採用複合主鍵的形式，其中使用分割區索引鍵和排序索引鍵的組合來唯一定義項目。分割區索引鍵會經過雜湊處理，以判斷資料的實體位置並加以擷取。因此，請務必選擇高基數且可水平擴充的屬性作為分割區索引鍵，以確保資料均勻分佈。排序索引鍵屬性在索引鍵結構描述中是選用項目，而且有排序索引鍵就可讓您在 DynamoDB 中建立一對多關係的模型，以及建立項目集合。排序索引鍵也稱為範圍索引鍵，可用來排序項目集合中的項目，也能進行靈活的範圍型操作。

如需 DynamoDB 索引鍵結構描述的詳細資訊和最佳實務，可參考下列內容：
+ [DynamoDB 的分割區與資料分配](HowItWorks.Partitions.md) 
+ [在 DynamoDB 中設計與有效運用分割區索引鍵的最佳實務](bp-partition-key-design.md) 
+ [使用排序索引鍵組織 DynamoDB 資料的最佳實務](bp-sort-keys.md) 
+ [選擇適合的 DynamoDB 分割區索引鍵](https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/) 

通常需要有次要索引才能在 DynamoDB 中支援其他查詢模式。次要索引是影子資料表，其中相同的資料會透過與基礎資料表不同的索引鍵結構描述來組織。本機次要索引 (LSI) 與基礎資料表共用相同的分割區索引鍵，並允許使用替代的排序索引鍵來共用基礎資料表的容量。全域次要索引 (GSI) 可以有與基礎資料表不同的分割區索引鍵以及不同的排序索引鍵屬性，這表示 GSI 的輸送量管理與基礎資料表無關。

如需次要索引和最佳實務的進一步詳細資訊，可參考下列內容：
+ [在 DynamoDB 中使用次要索引來改善資料存取](SecondaryIndexes.md) 
+ [使用 DynamoDB 中次要索引的最佳實務](bp-indexes.md) 

現在讓我們來進一步了解資料建模。在 DynamoDB 或任何類似 NoSQL 資料庫上設計靈活且高度最佳化結構描述的程序，可能是一項要學習的極具挑戰性技能。本單元的目標是協助您發展心智圖，以設計讓您從使用案例進入生產環境的結構描述。我們將首先介紹任何設計 (單一資料表與多資料表設計) 的基礎選擇。接著，我們會檢閱多種設計模式 (建置區塊)，這些模式可供您的應用程式用來實現各種組織性或效能結果。最後，我們會介紹適用於不同使用案例和產業的各種完整結構描述設計套件。

![\[此圖顯示資料間的概念關係、位於其下方的區塊，以及區塊下方的基礎。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesign.png)


**Topics**
+ [物品集合 - 如何在 DynamoDB 中建立一對多關係的模型](WorkingWithItemCollections.md)
+ [DynamoDB 中的資料建模基礎](data-modeling-foundations.md)
+ [DynamoDB 中的資料建模建置區塊](data-modeling-blocks.md)
+ [DynamoDB 中的資料建模結構描述設計套件](data-modeling-schemas.md)
+ [在 DynamoDB 中製作關聯式資料模型的最佳實務](bp-relational-modeling.md)

# 物品集合 - 如何在 DynamoDB 中建立一對多關係的模型
<a name="WorkingWithItemCollections"></a>

在 DynamoDB 中，*物品集合*是共享相同分割區索引鍵值的物品群組，這表示這些物品是相關聯的。物品集合是在 DynamoDB 中建立一對多關係模型的主要機制。物品集合只能存在於配置為使用[複合主鍵](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey)的資料表或索引。

**注意**  
物品集合可以存在於基底資料表中，也可以存在於次要索引中。有關物品集合如何與索引互動的詳細資訊，請參閱 [本機次要索引中的項目集合](LSI.md#LSI.ItemCollections)。

請考慮下表，其中顯示三位不同的使用者及其遊戲內物品清單：

![\[具有不同屬性的三種不同物品集合。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/item_collection.png)


對於每個集合中的特定物品，排序索引鍵是由用於將資料分組的資訊 (例如 `inventory::armor`、`inventory::weapon` 或 `info`) 組成的串連。每個物品集合可以具有不同組合的屬性作為排序索引鍵。使用者 `account1234` 具有 `inventory::weapons` 物品，而使用者 `account1387` 沒有 (因為他們還沒有找到)。使用 `account1138` 只使用兩個物品作為排序索引鍵 (因為他們還沒有物品清單)，而其他使用者使用三個物品。

DynamoDB 允許您選擇性地從這些物品集合中擷取物品，以執行以下操作：
+ 從特定使用者擷取所有物品
+ 從特定使用者擷取一個物品
+ 擷取屬於特定使用者的特定類型的所有物品

## 使用物品集合組織資料來加快查詢
<a name="WorkingWithItemCollections.Example"></a>

在此範例中，這三個物品集合中的每個物品都代表一個玩家，和我們根據遊戲和玩家的存取模式選擇的資料模型。遊戲需要什麼資料？ 什麼時候需要？ 需要的頻率為何？ 這樣做的成本是多少？ 這些資料建模決策是根據這些問題的答案做出。

在這個遊戲中，有一個頁面為玩家呈現其武器庫存清單，還有另一個頁面呈現盔甲。玩家開啟他們的庫存清單時，會先顯示武器，因為我們希望該頁面能夠以極快速度載入，之後才會載入後續的庫存清單頁面。隨著玩家獲得更多的遊戲內物品，這些物品類型可能會相當大，因此我們決定每個庫存清單頁面在資料庫的玩家物品集合中，都獨立自成一個項目。

以下區段詳細介紹如何透過 `Query` 操作與物品集合互動。

**Topics**
+ [使用物品集合組織資料來加快查詢](#WorkingWithItemCollections.Example)

# DynamoDB 中的資料建模基礎
<a name="data-modeling-foundations"></a>

本節藉由檢驗兩種類型的資料表設計來介紹基礎層：單一資料表和多資料表。

![\[此圖顯示資料間的概念關係、位於其下方的區塊，以及區塊下方的基礎。強調基礎。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignFoundation.png)


## 單一資料表設計基礎
<a name="data-modeling-foundations-single"></a>

在我們 DynamoDB 結構描述的基礎上，其中一個選擇是**單一資料表設計**。單一資料表設計是一種模式，可讓您將多種資料類型 (實體) 儲存在單一 DynamoDB 資料表中。它旨在透過消除維護多個資料表和它們之間複雜關係的需要，來最佳化資料存取模式，提高效能並降低成本。這是有可能的，因為 DynamoDB 會將具有相同分割區索引鍵 (稱為項目集合) 的項目儲存在彼此相同的分割區上。在此設計中，不同類型的資料會以項目的形式儲存在同一個資料表中，並且每個項目由一個唯一的排序索引鍵來識別。

![\[本圖顯示資料表，以及如何使用排序索引鍵，透過相同 UserID 項目集合中的實體類型來區分每個項目。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SingleTableSchema.png)


**優點**
+ 資料位置性，可在單一資料庫呼叫中支援多個實體類型的查詢
+ 降低讀取的整體財務和延遲成本：
  + 對總計小於 4KB 的兩個項目的單一查詢是 0.5 RCU 最終一致
  + 對總計小於 4KB 的兩個項目的兩個查詢是 1 RCU 最終一致 (每個 0.5 RCU)
  + 傳回兩個單獨資料庫呼叫的平均時間將高於單一呼叫
+ 減少要管理的資料表數量：
  + 不需要跨多個 IAM 角色或政策維護許可 
  + 資料表的容量管理會在所有實體中進行平均，通常會產生更可預測的取用模式
  + 監控需要更少的警示
  + 客戶管理的加密金鑰只需在一個資料表上進行輪換
+ 資料表的平滑流量：
  + 透過將多種使用模式彙總到同一個資料表，整體使用量往往更平滑 (股票指數的績效往往比任何個股表現更平滑)，這對於透過佈建模式資料表達到更高的使用率有更好的效果

**缺點**
+ 與關聯式資料庫相比，由於矛盾的設計，學習曲線可能會很陡峭
+ 所有實體類型的資料需求必須一致
  + 備份是採用全有或全無原則，因此如果資料不是關鍵任務資料，請考慮將其保存在單獨的資料表中
  + 資料表加密會在所有項目之間共用。對於具有個別租戶加密需求的多租戶應用程式，將需要用戶端加密
  + 混合了歷史資料和操作資料的資料表，不會因啟用不常存取的儲存類別而獲得更多優勢。如需詳細資訊，請參閱[DynamoDB 資料表類別](HowItWorks.TableClasses.md) 
+ 即使只需處理一部分實體，所有變更的資料都會傳送至 DynamoDB Streams。
  + 由於有 Lambda 事件篩選條件，這在使用 Lambda 時不會影響您的帳單，但在使用 Kinesis Consumer Library 時會增加成本 
+ 使用 GraphQL 時，單一資料表設計將更加難以實現
+ 如果您使用更高層級的 SDK 客戶端 (如 Java 的 [`DynamoDBMapper`](DynamoDBMapper.md) 或[增強型用戶端](DynamoDBEnhanced.md))，那處理結果會比較困難，因為同一回應中的項目可能與不同的類別有關

**使用情況**

單一資料表設計非常適合經常同時查詢多個實體類型的應用程式，或需要維護不同資料類型之間關係的應用程式。如果您的存取模式受益於資料地區性，且您想要盡可能減少管理多資料表的額外負荷，這種設計就特別有效。

## 多資料表設計基礎
<a name="data-modeling-foundations-multi"></a>

在我們 DynamoDB 結構描述的基礎上，第二個選擇是多資料表設計****。多資料表設計是一種模式，更像是傳統資料庫設計，您可以在每個 DynamoDB 資料表中儲存單一類型 (實體) 資料。每個資料表中的資料仍會依據分割索引鍵進行組織，因此單一實體類型內的效能會針對可擴展性和效能進行最佳化，但是跨多資料表的查詢必須獨立完成。

![\[本圖顯示包含論壇清單的論壇資料表和一些彙整資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/MultipleTable1.png)


![\[本圖顯示討論串資料表，其中包含由它們所屬的特定論壇分割區的討論串清單。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/MultipleTable2.png)


**優點**
+ 對於那些不習慣使用單一資料表設計的人來說，該設計更簡單 
+ 由於每個解析程式會對應到單一實體 (資料表)，因此 GraphQL 解析程式的實現更容易
+ 允許跨不同實體類型的唯一資料需求：
  + 可為關鍵任務的單一資料表進行備份 
  + 可以每個資料表進行管理的資料表加密。對於具有個別租戶加密需求的多租戶應用程式，不同的租戶資料表可讓每個客戶擁有自己的加密金鑰
  + 不常存取的儲存類別只能在具有歷史資料的資料表上啟用，以實現完整的成本節省優勢。如需詳細資訊，請參閱[DynamoDB 資料表類別](HowItWorks.TableClasses.md)
+ 每個資料表都有自己的變更資料串流，允許針對每種類型的項目 (而非單一整合式處理器) 設計專用的 Lambda 函式

**缺點**
+ 對於需要跨多資料表資料的存取模式，則需要從 DynamoDB 進行多次讀取，而且可能需要在用戶端程式碼上處理/加入資料。
+ 對多資料表進行操作和監控需要更多 CloudWatch 警示，並且每個資料表必須獨立進行擴展
+ 每個資料表的許可都需要單獨管理。未來新增資料表都將需要變更任何必要的 IAM 角色或政策

**使用情況**

如果您的應用程式的存取模式不需要一併查詢多個實體或資料表，那麼多資料表設計會是良好且足夠的方法。

# DynamoDB 中的資料建模建置區塊
<a name="data-modeling-blocks"></a>

本節介紹建置區塊層，為您提供了可在應用程式中使用的設計模式。

![\[此圖顯示資料間的概念關係、位於其下方的區塊，以及區塊下方的基礎。強調基礎。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignBlocks.png)


**Topics**
+ [複合排序索引鍵建置區塊](#data-modeling-blocks-composite)
+ [多租用建置區塊](#data-modeling-blocks-multi-tenancy)
+ [稀鬆索引建置區塊](#data-modeling-blocks-sparse-index)
+ [建置區塊生存時間](#data-modeling-blocks-ttl)
+ [封存建置區塊生存時間](#data-modeling-blocks-ttl-archival)
+ [垂直分割建置區塊](#data-modeling-blocks-vertical-partitioning)
+ [寫入碎片建置區塊](#data-modeling-blocks-write-sharding)

## 複合排序索引鍵建置區塊
<a name="data-modeling-blocks-composite"></a>

當人們思考 NoSQL 時，他們也可能認為它是非關聯式的。最終，DynamoDB 結構描述中無法放入關係是不合理的，它們只是看起來與關聯式資料庫及其外部索引鍵不同而已。其中一項可以用來在 DynamoDB 中開發資料邏輯階層的最關鍵模式，就是複合排序索引鍵。最常見的設計模式，是以井字號將階層中的每一層 (父層 > 子層 > 孫層) 隔開。例如 `PARENT#CHILD#GRANDCHILD#ETC`。

![\[此圖顯示資料表中以 UserID 作為主索引鍵，並以其他屬性組合作為排序索引鍵的項目。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ShoppingCart.png)


雖然 DynamoDB 中的分割區索引鍵一律需要確切的值才能查詢資料，但我們可以將部分條件從左至右套用至排序索引鍵，類似於周遊二元樹。

在上面的範例中，我們有一間需要在使用者工作階段中維護購物車的電子商務商店。每當使用者登入時，他們可能希望看到整個購物車，包括儲存供日後使用的項目。但是，在他們進入結帳程序時，只應載入使用中購物車內的項目以供購買。由於這兩個 `KeyConditions` 都明確要求 CART 排序索引鍵，因此 DynamoDB 會在讀取時直接忽略額外的願望清單資料。雖然儲存的項目和使用中的項目都屬於同一個購物車，但我們需要在應用程式的不同部分中以不同的方式處理它們，因此在排序索引鍵的字首上套用 `KeyCondition`，是僅擷取應用程式每個部分所需資料的最佳方式。

**此建置區塊的主要特徵**
+ 相關項目彼此在本機儲存，以便有效存取資料 
+ 使用 `KeyCondition` 表達式，可以選擇性擷取階層的子集，表示沒有浪費的 RCU 
+ 應用程式的不同部分可以將其項目儲存在特定字首下，防止被覆寫的項目或衝突的寫入

## 多租用建置區塊
<a name="data-modeling-blocks-multi-tenancy"></a>

許多客戶使用 DynamoDB 來託管多租戶應用程式的資料。針對這些案例，我們想要設計結構描述，以便將單一租戶的所有資料保留在其本身的資料表邏輯分割區中。這會利用「項目集合」的概念，該術語是指 DynamoDB 資料表中具有相同分割區索引鍵的所有項目。如需 DynamoDB 如何處理多租戶的詳細資訊，請參閱 [Multitenancy on DynamoDB](https://docs.aws.amazon.com/whitepapers/latest/multi-tenant-saas-storage-strategies/multitenancy-on-dynamodb.html)。

![\[此圖顯示可代表多租戶相片網站的資料表。主索引鍵是由使用者作為分割區索引鍵和不同相片作為排序索引鍵組成。每個項目的屬性都會顯示相片託管所在 URL。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/MultiTenant.png)


在此範例中，我們正在執行一個可能有成千上萬使用者的相片託管網站。每個使用者最初只會將相片上傳到自己的個人資料，但預設情況下，我們不允許使用者查看任何其他使用者的相片。理想情況下，應該為每個使用者呼叫 API 的授權新增額外隔離等級，以確保他們只從自己的分割請求資料，但在結構描述等級中，唯一分割區索引鍵就足夠了。

**此建置區塊的主要特徵**
+ 任何一位使用者或租用戶讀取的資料量只能與其分割區中的項目總量相同
+ 由於帳戶關閉或合規要求而需刪除租戶資料，可以巧妙而廉價的方式完成。只要執行分割區索引鍵等於其租戶 ID 的查詢，然後為傳回的每個主索引鍵執行 `DeleteItem` 操作

**注意**  
專為多租用戶而設計，您可以在單一資料表中使用不同的加密金鑰提供者來安全地隔離資料。適用於 Amazon DynamoDB 的[AWS 資料庫加密 SDK](https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/what-is-database-encryption-sdk.html) 可讓您在 DynamoDB 工作負載中納入用戶端加密。您可以執行屬性層級加密，讓您在將特定屬性值儲存在 DynamoDB 資料表之前先加密，並搜尋加密的屬性，而無需事先解密整個資料庫。

## 稀鬆索引建置區塊
<a name="data-modeling-blocks-sparse-index"></a>

有時，存取模式需要尋找符合罕見項目的項目，或是接收狀態的項目 (需要提升回應)。我們不會定期查詢這些項目的整個資料集，而是可以利用**全域次要索引 (GSI)** 稀疏的載入資料的事實。這表示只有基礎資料表中具有在索引中定義之屬性項目，才會複寫至索引。

![\[本圖顯示接收大量穩定狀態資料的基礎資料表\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SparseBaseTable.png)


![\[本圖顯示僅接收已提升項目的全域次要索引\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SparseGSI.png)


在此範例中，我們可看到 IOT 使用案例，在欄位中的每個裝置都會定期回報狀態。對於大多數報告，我們期望裝置會報告一切正常，但有時可能會出現故障，必須提升給維修技術人員處理。對於具有升級的報告而言，系統會新增該項目的屬性 `EscalatedTo`，但不會顯示該屬性。本範例中的 GSI 由 `EscalatedTo` 分割，由於 GSI 會從基礎資料表中引入金鑰，我們仍然可以看到哪個 DeviceID 報告了故障以及何時報告故障。

雖然在 DynamoDB 中的讀取成本比寫入低，但稀疏索引是非常強大的工具，適用於特定類型項目的執行個體罕見，但讀取以找到它們卻很常見的使用案例。

**此建置區塊的主要特徵**
+ 稀疏 GSI 的寫入和儲存成本僅適用於符合金鑰模式的項目，因此 GSI 的成本可以大幅低於已複製所有項目的其他 GSI 
+ 複合排序索引鍵仍可用來進一步縮小符合所需查詢的項目範圍，例如，排序索引鍵可以使用時間戳記，以便只檢視最後 X 分鐘內報告的錯誤 (`SK > 5 minutes ago, ScanIndexForward: False`)

## 建置區塊生存時間
<a name="data-modeling-blocks-ttl"></a>

大多數資料都有一定的持續時間，可以視為值得保存在主要資料儲存中。為了協助在 DynamoDB 中的資料老化，它具有稱作**存留時間 (TTL)** 的功能。[TTL](TTL.md) 功能可讓您在資料表層級定義特定屬性，這些屬性需要監視具有 epoch 時間戳記 (已為過去的事) 的項目。這使您可以免費從資料表中刪除過期記錄。

**注意**  
如果您使用[全域資料表版本 2019.11.21 (目前版本)](GlobalTables.md) 且您也使用[生存時間](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html)功能，DynamoDB 會複製 TTL 刪除至所有資料表複本。初始 TTL 刪除不會在 TTL 過期發生時消耗區域中的寫入容量。但是，對複本資料表複寫的 TTL 刪除會消耗每個複本區域中的複寫寫入容量，並且將收取適當的費用。

![\[本圖顯示具有存留時間屬性的使用者訊息資料表\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/TTL.png)


在此範例中，我們有一個應用程式專門設計用來讓使用者建立短期訊息。在 DynamoDB 中建立訊息時，應用程式的程式碼會將 TTL 屬性設定為未來七天的日期。在大約七天內，DynamoDB 會看到這些項目的 epoch 時間戳記已經過去，並將其刪除。

由於 TTL 完成的刪除是免費的，因此強烈建議您使用此功能從資料表中移除歷史資料。這將減少每個月的整體儲存體費用，並可能降低使用者讀取成本，因為他們查詢擷取的資料將較少。雖然 TTL 是在資料表層級啟用的，但要為哪些項目或實體建立 TTL 屬性，以及將 epoch 時間戳記設定為未來多久時間，將由您決定。

**此建置區塊的主要特徵**
+ TTL 刪除會在幕後執行，不會影響您的資料表效能 
+ TTL 是一種非同步處理程序，大約每六個小時執行一次，但可能需要超過 48 小時才能刪除過期的記錄 
  + 如果必須在 48 小時內清除過時的資料，請勿在鎖定記錄或狀態管理等使用案例中依賴 TTL 進行刪除 
+ 您可以將 TTL 屬性命名為有效的屬性名稱，但值必須是數字類型

## 封存建置區塊生存時間
<a name="data-modeling-blocks-ttl-archival"></a>

雖然 TTL 是從 DynamoDB 刪除舊資料的有效工具，但許多使用案例都需要將資料封存的保存時間比主要資料儲存更長。在此情況下，我們可以利用 TTL 對記錄的定時刪除，將過期的記錄推送到長期資料儲存。

![\[此圖顯示的資料表會將即時刪除工作的時間傳送至 DynamoDB 串流，然後再傳送長期資料儲存。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/TTLArchive.png)


在 DynamoDB 完成 TTL 刪除時，它仍會作為 `Delete` 事件推送到 DynamoDB 串流中。在 DynamoDB TTL 是執行刪除的執行者時，`principal:dynamodb` 的串流記錄上會有一個屬性。使用 Lambda 訂閱者到 DynamoDB Stream，我們只能為 DynamoDB 主體屬性套用事件篩選條件，並知道符合該篩選條件的任何記錄都會推送到封存存放區，例如 Amazon Glacier。

**此建置區塊的主要特徵**
+  一旦歷史項目不再需要 DynamoDB 的低延遲讀取，將它們遷移到較冷的儲存服務，例如 Amazon Glacier，可以大幅降低儲存成本，同時滿足使用案例的資料合規需求 
+ 如果資料保留在 Amazon S3 中，則可以使用具成本效益的分析工具 (如 Amazon Athena 或 Redshift Spectrum) 來執行資料的歷史分析

## 垂直分割建置區塊
<a name="data-modeling-blocks-vertical-partitioning"></a>

熟悉文件模型資料庫的使用者將熟悉在單一 JSON 文件中儲存所有相關資料的想法。雖然 DynamoDB 支援 JSON 資料類型，但不支援在巢狀 JSON 上執行 `KeyConditions`。由於 `KeyConditions` 會決定要從磁碟讀取多少資料，以及查詢有效消耗了多少 RCU，因此可能會導致大規模的不足。為了更最佳化 DynamoDB 的寫入和讀取，我們建議您將文件的個別實體分割為個別 DynamoDB 項目，也稱為**垂直分割**。

![\[此圖顯示格式化為巢狀 JSON 物件的大型資料結構。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DocumentBlob.png)


![\[此圖顯示項目集合，其中項目的排序索引鍵有助於保持 DynamoDB 使用率最佳化。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SingleTableSchema.png)


如上所示，垂直分割是實際操作中單一資料表設計的關鍵範例，但如果需要，也可以跨多個資料表進行實作。由於 DynamoDB 帳單會以 1 KB 的增量進行寫入，因此理想情況下，您應該以產生小於 1 KB 項目的方式對文件進行分割。

**此建置區塊的主要特徵**
+ 資料關係的階層是透過排序索引鍵字首維護的，因此如果需要，可以在用戶端重建單一文件結構 
+ 資料結構的單數元件可以獨立更新，導致小項目更新僅為 1 WCU 
+ 透過使用排序索引鍵 `BeginsWith`，應用程式可在單一查詢中擷取類似資料，彙總讀取成本以降低總成本/延遲
+ 大型文件可輕易地超過 DynamoDB 中的 400 KB 個別項目大小限制，而垂直分割則有助於解決此限制

## 寫入碎片建置區塊
<a name="data-modeling-blocks-write-sharding"></a>

DynamoDB 的極少數硬性限制之一，就是單一實體分割區每秒可維持多少輸送量 (不一定是單一分割區索引鍵) 的限制。這些限制目前是：
+ 1000 個 WCU (或每秒寫入 1000 <= 1KB 的項目) 和 3000 RCU (或每秒讀取 3000 <= 4KB) *強烈一致*或 
+ 每秒 6000 <= 4 KB 讀取*最終一致*

如果對資料表的要求超過上述任一限制，則會將錯誤傳回給 `ThroughputExceededException` 的用戶端 SDK，通常稱為限流。需要超過該限制的讀取操作使用案例，通常是將讀取快取放在 DynamoDB 前面，以達到最佳效果，但是寫入操作需要稱為**寫入碎片**的結構描述層級設計。

![\[此圖顯示 DynamoDB 如何跨多個分割區對分割區索引鍵進行碎片化處理，以防止流量尖峰造成限流。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/WriteShardingProblem.png)


![\[此圖顯示 DynamoDB 如何跨多個分割區對分割區索引鍵進行碎片化處理，以防止流量尖峰造成限流。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/WriteShardingSolution.png)


為了解決此問題，我們將在應用程式的 `UpdateItem` 代碼中的每個競爭者的分割區索引鍵結尾附加一個隨機整數。隨機整數產生器的範圍，將需要具有上限相符或超過指定競爭者每秒的寫入量除以 1000。為了支援每秒 20,000 次投票，它看起來會像 rand (0,19)。現在該資料儲存在單獨的邏輯分割區下，它必須在讀取時重新合併在一起。由於投票總數不需要是即時的，因此排定為每 X 分鐘讀取所有投票分割區的 Lambda 函式可能會偶爾為每位競爭者執行彙總，並將其寫回單一投票總記錄以供即時讀取。

**此建置區塊的主要特徵**
+ 對於無法避免的指定分割區索引鍵具有極高寫入輸送量的使用案例，可以人為將寫入操作分散到多個 DynamoDB 分割區 
+ 具有低基數分割區索引鍵的 GSI 也應該使用此模式，因為 GSI 上的限流會套用背壓，以在基礎資料表上進行寫入操作

# DynamoDB 中的資料建模結構描述設計套件
<a name="data-modeling-schemas"></a>

了解 DynamoDB 的資料建模結構描述設計套件，包括社群網路、遊戲設定檔、投訴管理、週期性付款、裝置狀態和線上商店的使用案例、存取模式和最終結構描述設計。

![\[此圖顯示資料間的概念關係、位於其下方的區塊，以及區塊下方的基礎。強調基礎。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignData.png)


## 先決條件
<a name="data-modeling-prereqs"></a>

在嘗試為 DynamoDB 設計結構描述之前，我們必須先收集有關結構描述需要支援的使用案例的一些先決條件資料。與關聯式資料庫不同，DynamoDB 預設會碎片化，這表示資料會在幕後存放在多部伺服器上，因此針對資料位置進行設計非常重要。我們需要為每個結構描述設計整理以下清單：
+ 實體清單 (ER 圖)
+ 每個實體的預估數量和輸送量
+ 需要被支援的存取模式 (查詢和寫入)
+ 資料保留要求

**Topics**
+ [先決條件](#data-modeling-prereqs)
+ [DynamoDB 中的社交網路結構描述設計](data-modeling-schema-social-network.md)
+ [DynamoDB 中的遊戲設定檔結構描述設計](data-modeling-schema-gaming-profile.md)
+ [DynamoDB 中的投訴管理系統結構描述設計](data-modeling-complaint-management.md)
+ [DynamoDB 中的週期性付款結構描述設計](data-modeling-schema-recurring-payments.md)
+ [監控 DynamoDB 中的裝置狀態更新](data-modeling-device-status.md)
+ [使用 DynamoDB 做為線上商店的資料存放區](data-modeling-online-shop.md)

# DynamoDB 中的社交網路結構描述設計
<a name="data-modeling-schema-social-network"></a>

## 社交網路商業使用案例
<a name="data-modeling-schema-social-network-use-case"></a>

此使用案例討論如何使用 DynamoDB 做為社交網路。社交網絡是一種線上服務，可以讓不同的使用者與彼此互動。我們將設計的社交網路會讓使用者看到一個時間軸，其中包括他們的文章、他們的跟隨者、他們跟隨的人，以及他們跟隨的人撰寫的文章。此結構描述設計的存取模式為：
+ 取得指定 userID 的使用者資訊 
+ 取得指定 userID 的跟隨者清單
+ 取得指定 userID 的跟隨清單
+ 取得指定 userID 的文章清單
+ 取得指定 postID 中喜歡該篇文章的使用者清單
+ 取得指定 postID 的喜歡計數
+ 取得指定 userID 的時間軸

## 社交網路實體關係圖
<a name="data-modeling-schema-social-network-erd"></a>

這是我們將用於社交網路結構描述設計的實體關係圖 (ERD)。

![\[顯示使用者、文章和跟隨者等實體的社交網路應用程式 ERD。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetworkERD.png)


## 社交網路存取模式
<a name="data-modeling-schema-social-network-access-patterns"></a>

這些是我們針對社交網路結構描述設計考量的存取模式。
+ `getUserInfoByUserID`
+ `getFollowerListByUserID`
+ `getFollowingListByUserID`
+ `getPostListByUserID`
+ `getUserLikesByPostID`
+ `getLikeCountByPostID`
+ `getTimelineByUserID`

## 社交網路結構描述設計演變
<a name="data-modeling-schema-social-network-design-evolution"></a>

DynamoDB 是一種 NoSQL 資料庫，因此它不允許您執行聯結，這是一種結合多個資料庫資料的操作。不熟悉 DynamoDB 的客戶可能會在不需要時，將關聯式資料庫管理系統 (RDBMS) 設計理念 (例如為每個實體建立資料表) 套用至 DynamoDB。DynamoDB 的單一資料表設計之目的，是根據應用程式的存取模式，以預先聯結的形式寫入資料，然後立即使用資料，無需額外運算。如需詳細資訊，請參閱 [DynamoDB 中的單一資料表與多資料表設計](https://aws.amazon.com/blogs/database/single-table-vs-multi-table-design-in-amazon-dynamodb/)。

現在，讓我們逐步介紹如何發展結構描述設計以解決所有存取模式。

**步驟 1：位址存取模式 1 (`getUserInfoByUserID`)**

為了取得特定使用者的資訊，我們需要 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) 基礎資料表，索引鍵條件為 `PK=<userID>`。查詢操作可讓您將結果分頁，這在使用者有許多跟隨者的情況下很實用。如需查詢的詳細資訊，請參閱 [在 DynamoDB 中查詢資料表](Query.md)。

在我們的範例中，我們追蹤使用者的兩種類型資料：他們的「計數」和他們的「資訊」。使用者的「計數」反映他們有多少追隨者，他們追隨的使用者數量以及他們建立了多少文章。使用者的「資訊」反映了他們的個人資訊，例如他們的姓名。

我們看到這兩種資料由以下的兩個項目來表示。排序索引鍵 (SK) 中有「計數」的項目比含有「資訊」的項目更有可能變更。DynamoDB 會將項目的大小視為更新前後所顯示的大小，消耗的佈建輸送量將反映這些項目大小中較大的部分。因此即使您只更新一小部分項目的屬性，[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) 還是會使用完整數量的佈建輸送量 (之前和之後項目大小中較大的一個)。您可以透過單一 `Query` 操作取得項目，並使用 `UpdateItem` 從現有的數字屬性中新增或減去。

![\[ID u#12345 使用者的查詢操作結果及其計數和資訊資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork1.png)


**步驟 2：位址存取模式 2 (`getFollowerListByUserID`)**

若要取得跟隨指定使用者的使用者清單，我們需要 `Query` 基礎資料表，索引鍵條件為 `PK=<userID>#follower`。

![\[資料表的查詢操作結果，列出 ID u#12345 使用者的跟隨者。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork2.png)


**步驟 3：位址存取模式 3 (`getFollowingListByUserID`)**

若要取得指定使用者跟隨的使用者清單，我們需要 `Query` 基礎資料表，索引鍵條件為 `PK=<userID>#following`。然後您可以使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 操作將多項請求結合為一組，並執行以下操作：
+ 將使用者 A 新增到使用者 B 的跟隨者清單中，然後將使用者 B 的跟隨者計數增加一。
+ 將使用者 B 新增到使用者 A 的跟隨者清單中，然後將使用者 A 的跟隨者計數增加一。

![\[資料表的查詢操作結果，列出 ID u#12345 使用者跟隨的所有使用者。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork3.png)


**步驟 4：位址存取模式 4 (`getPostListByUserID`)**

若要取得指定使用者建立的文章清單，我們需要 `Query` 基礎資料表，索引鍵條件為 `PK=<userID>#post`。這裡需要注意的一件重要事情是，使用者的 postID 必須是增量的：第二個 postID 值必須大於第一個 postID 值 (因為使用者希望以排序方式查看自己的貼文)。您可以透過根據時間值 (例如通用唯一字典順序排序識別符 (ULID)) 產生 postID 來實現此目的。

![\[使用索引鍵條件的查詢操作結果，以取得特定使用者建立的文章清單。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork4.png)


**步驟 5：位址存取模式 5 (`getUserLikesByPostID`)**

若要取得對指定使用者貼文按下喜歡的使用者清單，我們需要 `Query` 基礎資料表，索引鍵條件為 `PK=<postID>#likelist`。這種方法是我們用於擷取跟隨者和跟隨清單中存取模式 2 (`getFollowerListByUserID`) 和存取模式 3 (`getFollowingListByUserID`) 相同的模式。

![\[使用索引鍵條件的查詢操作結果，以取得喜歡特定使用者文章的使用者清單。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork5.png)


**步驟 6：位址存取模式 6 (`getLikeCountByPostID`)**

要取得指定文章的喜歡計數，我們需要透過 `PK=<postID>#likecount` 的索引鍵條件在基礎資料表上執行 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 操作。每當有許多跟隨者 (例如名人) 的使用者建立文章時，此存取模式就會造成限流問題，因為當分割區的輸送量超過每秒 1000 WCU 時，就會發生限流問題。這個問題不是 DynamoDB 的結果，它只是出現在 DynamoDB 中，因為它位於軟體堆疊的末端。

您應該評估它是否對所有使用者同時查看類似計數真的很重要，或者是否可以隨著時間的推移逐漸發生。一般來說，文章的類似計數不需要立即 100% 準確。您可以在應用程式和 DynamoDB 之間放置佇列，以便定期進行更新以實作此策略。

![\[使用索引鍵條件的 GetItem 操作結果，以取得特定文章的喜歡計數。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork6.png)


**步驟 7：位址存取模式 7 (`getTimelineByUserID`)**

如要取得指定使用者的時間軸，我們需要透過 `PK=<userID>#timeline` 的索引鍵條件在基礎資料表上執行 `Query` 操作。讓我們考慮一個場景，即使用者的追隨者需要同步查看他們的文章。每次使用者寫一篇文章時，他們的跟隨者清單都會被讀取，並且他們的 userID 和 postID 會慢慢輸入到其所有跟隨者的時間軸索引鍵中。然後，當您的應用程式啟動時，您可以使用 `Query` 操作讀取時間軸索引鍵，並使用任何新項目的 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) 操作來使用 userID 和 postID 的組合填充時間軸畫面。您無法使用 API 呼叫讀取時間軸，但是如果文章可以經常編輯，則這是一個更具成本效益的解決方案。

時間軸是一個顯示最近文章的地方，因此我們需要一種清理舊文章的方法。您可以使用 DynamoDB 的 [TTL](TTL.md) 功能免費執行此操作，而不是使用 WCU 刪除它們。

![\[使用索引鍵條件的查詢操作結果，以取得特定使用者顯示最近文章的時間軸。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork7.png)


下表摘要整理了所有存取模式，以及結構描述設計處理這些模式的方式：


| 存取模式 | 基礎資料表/GSI/LSI | 作業 | 分割區索引鍵值 | 排序索引鍵值 | 其他條件/篩選條件 | 
| --- | --- | --- | --- | --- | --- | 
| getUserInfoByUserID | 基礎資料表 | Query | PK=<userID> |  |  | 
| getFollowerListByUserID | 基礎資料表 | Query | PK=<userID>\$1follower |  |  | 
| getFollowingListByUserID | 基礎資料表 | Query | PK=<userID>\$1following |  |  | 
| getPostListByUserID | 基礎資料表 | Query | PK=<userID>\$1post |  |  | 
| getUserLikesByPostID | 基礎資料表 | Query | PK=<postID>\$1likelist |  |  | 
| getLikeCountByPostID | 基礎資料表 | GetItem | PK=<postID>\$1likecount |  |  | 
| getTimelineByUserID | 基礎資料表 | Query | PK=<userID>\$1timeline |  |  | 

## 社交網路最終結構描述
<a name="data-modeling-schema-social-network-final-schema"></a>

這是最終的結構描述設計。若要將此結構描述設計下載為 JSON 檔案，請參閱 GitHub 上的 [DynamoDB 範例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/SocialNetwork/SocialNetworkSchema.json)。

**基礎資料表：**

![\[資料表的最終結構描述設計，其中包含上述查詢和 GetItem 操作的結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork8.png)


## 使用 NoSQL Workbench 與此結構描述設計
<a name="data-modeling-schema-social-network-nosql"></a>

您可以將此最終結構描述匯入 [NoSQL Workbench](workbench.md)，這是為 DynamoDB 提供資料建模、資料視覺化，和查詢開發功能的視覺化工具，以進一步探索和編輯新專案。請依照下列步驟以開始使用：

1. 下載 NoSQL Workbench。如需詳細資訊，請參閱[下載 DynamoDB 專用 NoSQL Workbench](workbench.settingup.md)。

1. 下載上面列出的 JSON 結構描述檔案，該檔案已經是 NoSQL Workbench 模型格式。

1. 將 JSON 結構描述檔案匯入到 NoSQL Workbench。如需詳細資訊，請參閱[匯入現有的資料模型](workbench.Modeler.ImportExisting.md)。

1. 一旦您匯入到 NOSQL Workbench 後，便可以編輯資料模型。如需詳細資訊，請參閱[編輯現有的資料模型](workbench.Modeler.Edit.md)。

# DynamoDB 中的遊戲設定檔結構描述設計
<a name="data-modeling-schema-gaming-profile"></a>

## 遊戲設定檔商業使用案例
<a name="data-modeling-schema-gaming-profile-use-case"></a>

本使用案例討論如何使用 DynamoDB 儲存遊戲系統的玩家設定檔。使用者 (在本案例中為玩家) 需要先建立設定檔，然後才能與許多現代遊戲 (尤其是線上遊戲) 進行互動。遊戲設定檔通常包括下列項目：
+ 基本資訊，例如使用者名稱
+ 遊戲資料，例如物品和裝備
+ 遊戲記錄，例如任務和活動
+ 朋友清單等社交資訊

為了滿足此應用程式的精細資料查詢存取要求，主索引鍵 (分割區索引鍵和排序索引鍵) 將使用通用名稱 (PK 和 SK)，因此它們可以用各種類型的值進行過載，如下所示。

此結構描述設計的存取模式為：
+ 取得使用者的好友清單
+ 取得玩家的所有資訊
+ 取得使用者的物品清單
+ 從使用者的物品清單中取得特定項目
+ 更新使用者的角色
+ 更新使用者的物品計數

遊戲設定檔的大小在不同的遊戲中會有所不同。[壓縮大型屬性值](bp-use-s3-too.md)可以讓它們符合 DynamoDB 中的項目限制並減少成本。輸送量管理策略取決於各種因素，例如：玩家數量，每秒遊戲遊玩數量，以及工作負載的季節性。通常對於新推出的遊戲而言，玩家數量和受歡迎程度未知，因此我們將從[隨需輸送量模式](capacity-mode.md#capacity-mode-on-demand)開始。

## 遊戲設定檔實體關係圖
<a name="data-modeling-schema-gaming-profile-erd"></a>

這是我們將用於遊戲設定檔架構設計的實體關係圖 (ERD)。

![\[遊戲設定檔的 ER 圖表，顯示實體之間的關係，例如使用者、遊戲和分數。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfileERD.png)


## 遊戲設定檔存取模式
<a name="data-modeling-schema-gaming-profile-access-patterns"></a>

這些是我們針對社交網路結構描述設計考量的存取模式。
+ `getPlayerFriends`
+ `getPlayerAllProfile`
+ `getPlayerAllItems`
+ `getPlayerSpecificItem`
+ `updateCharacterAttributes`
+ `updateItemCount`

## 遊戲設定檔結構描述設計演進
<a name="data-modeling-schema-social-network-design-evolution"></a>

從上面的 ERD 我們可以看到，這是一個一對多關係類型的資料建模。在 DynamoDB 中，一對多資料模型可以組織成項目集合，這與傳統建立多資料表並透過外部索引鍵建立連結的關聯式資料庫不同。[項目集合](WorkingWithItemCollections.md)是一組項目，共用相同的分割區索引鍵值，但具有不同的排序索引鍵值。在項目集合中，每個項目都有唯一的排序索引鍵值，可將其與其他項目區分開來。考量這一點，讓我們對每種實體類型的 `HASH` 和 `RANGE` 值使用以下模式。

首先，我們使用通用名稱 (如 `PK` 和 `SK`) 將不同類型的實體儲存在同一個資料表中，以打造前瞻性的模型。為了更好的可讀性，我們可以包括字首來表示資料的類型或包含名為 `Entity_type` 或 `Type` 的任意屬性。在目前範例中，我們使用以 `player` 開頭的字串將 `player_ID` 儲存為 `PK`；使用 `entity name#` 作為 `SK` 的字首，並新增一個 `Type` 屬性來指示這筆資料是哪種實體類型。這使我們能夠支援將來儲存更多實體類型，並使用例如 GSI 超載和稀疏 GSI 等先進技術來滿足更多存取模式。

讓我們開始實作存取模式。新增玩家、新增裝備等存取模式，都可以透過 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 操作來實現，所以我們可以忽略它們。在本文件中，我們將著重於上面列出的典型存取模式。

**步驟 1：位址存取模式 1 (`getPlayerFriends`)**

我們會透過此步驟解決存取模式 1 (`getPlayerFriends`)。在我們目前的設計中，朋友關係很簡單，遊戲中的朋友數量也很少。為簡單起見，我們使用清單資料類型來儲存朋友清單 (1:1 模型建置)。在這種設計中，我們使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 來滿足這種存取模式。在 `GetItem` 操作中，我們明確提供了分割區索引鍵和排序索引鍵值以取得特定項目。

但是，如果一個遊戲有大量的朋友，並且他們之間的關係很複雜 (例如朋友關係是雙向的，具有邀請和接受元件)，則必須使用多對多關係來單獨儲存每個朋友，以便擴展到無限的朋友清單大小。而且，如果朋友關係變更涉及同時對多個項目進行操作，則 DynamoDB 交易可用於將多個動作分組在一起，並將它們提交為單一「全有或全無」[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 或 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html) 操作。

![\[朋友實體遊戲設定檔的複雜多對多關係圖。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile1.png)


**步驟 2：位址存取模式 2 (`getPlayerAllProfile`)、3 (`getPlayerAllItems`) 和 4 (`getPlayerSpecificItem`)**

我們使用此步驟解決存取模式 2 (`getPlayerAllProfile`)、3 (`getPlayerAllItems`) 和 4 (`getPlayerSpecificItem`)。這三種存取模式的共同點是使用 [`Query`](Query.md) 操作的範圍查詢。根據查詢的範圍，會使用[索引鍵條件](Query.KeyConditionExpressions.md)和[篩選條件表達式](Query.FilterExpression.md)，這些表達式通常在實際開發中使用。

在查詢操作中，我們為分割區索引鍵提供單一值，並取得具有該分割區索引鍵值的所有項目。存取模式 2 (`getPlayerAllProfile`) 是以這種方式實現。或者，我們可以新增一個排序索引鍵條件表達式 - 確定要從資料表中讀取項目的字串。存取模式 3 (`getPlayerAllItems`) 是透過新增排序索引鍵 begins\$1with `ITEMS#` 的索引鍵條件來實現。此外，為了簡化應用程式端的開發，我們可以使用篩選條件表達式來實現存取模式 4 (`getPlayerSpecificItem`)。

以下是使用篩選條件表達式來篩選 `Weapon` 類別項目的虛擬程式碼範例：

```
filterExpression: "ItemType = :itemType"
expressionAttributeValues: {":itemType": "Weapon"}
```

![\[以分割區索引鍵和排序索引鍵條件進行查詢操作，以實作不同的存取模式。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile2.png)


**注意**  
篩選條件表達式會在查詢完成之後，結果傳回給用戶端之前進行套用。因此，無論是否有篩選條件表達式，查詢都會使用相同數量的讀取容量。

如果存取模式是查詢大型資料集並篩選出大量資料以僅保留一小部分資料，則適當的方法是更有效地設計 DynamoDB 分割區索引鍵和排序索引鍵。例如，在上面的範例中取得特定 `ItemType`，如果每個玩家都有很多物品並且查詢特定 `ItemType` 是典型的存取模式，將 `ItemType` 作為複合索引鍵帶入 `SK` 會更有效。資料模型看起來會像這樣：`ITEMS#ItemType#ItemId`。

**步驟 3：位址存取模式 5 (`updateCharacterAttributes`)、和 6 (`updateItemCount`)**

我們使用此步驟解決存取模式 5 (`updateCharacterAttributes`) 和 6 (`updateItemCount`)。當玩家需要修改角色時，例如減少貨幣，或者修改物品中某種武器數量時，可使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) 以實現這些存取模式。如要更新玩家的貨幣，但確保它永遠不會低於最低金額，僅在餘額大於或等於最小金額時，我們才可以新增 [DynamoDB 條件表達式 CLI 範例](Expressions.ConditionExpressions.md) 來減少餘額。此為一個虛擬程式碼範例：

```
UpdateExpression: "SET currency = currency - :amount"
ConditionExpression: "currency >= :minAmount"
```

![\[使用 UpdateItem 搭配條件表達式來修改玩家貨幣，確保其絕不會小於設定金額。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile4-Update-player-Currency.png)


使用 DynamoDB 進行開發並使用[原子計數器](WorkingWithItems.md#WorkingWithItems.AtomicCounters)減少庫存時，我們可以透過使用樂觀鎖定來確保冪等性。以下是原子計數器的虛擬程式碼範例：

```
UpdateExpression: "SET ItemCount = ItemCount - :incr"
expression-attribute-values: '{":incr":{"N":"1"}}'
```

![\[使用原子計數器將 ItemCount 屬性值從 5 遞減至 4。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile5-Update-Item-Count.png)


此外，在玩家用貨幣購買物品的情況下，整個過程需要扣除貨幣並同時新增一個物品。我們可以使用 DynamoDB 交易將多個動作分組在一起，並將它們作為單一「全有或全無」`TransactWriteItems` 或 `TransactGetItems` 操作來提交。`TransactWriteItems` 是一種同步且冪等性的寫入操作，在單一「全有或全無」操作中分成多達 100 個群組的寫入操作。這些動作的完成具有不可分割性，也就是全部成功或全部失敗。交易有助於消除貨幣重複或消失的風險。如需交易的詳細資訊，請參閱 [DynamoDB 交易範例](transaction-example.md)。

下表摘要整理了所有存取模式，以及結構描述設計處理這些模式的方式：


| 存取模式 | 基礎資料表/GSI/LSI | 作業 | 分割區索引鍵值 | 排序索引鍵值 | 其他條件/篩選條件 | 
| --- | --- | --- | --- | --- | --- | 
| getPlayerFriends | 基礎資料表 | GetItem | PK=PlayerID | SK=“FRIENDS\$1playerID” |  | 
| getPlayerAllProfile | 基礎資料表 | Query | PK=PlayerID |  |  | 
| getPlayerAllItems | 基礎資料表 | Query | PK=PlayerID | SK begins\$1with “ITEMS\$1” |  | 
| getPlayerSpecificItem | 基礎資料表 | Query | PK=PlayerID | SK begins\$1with “ITEMS\$1” | filterExpression: "ItemType = :itemType" expressionAttributeValues: \$1 ":itemType": "Weapon" \$1 | 
| updateCharacterAttributes | 基礎資料表 | UpdateItem | PK=PlayerID | SK=“\$1METADATA\$1playerID” | UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount" | 
| updateItemCount | 基礎資料表 | UpdateItem | PK=PlayerID | SK =“ITEMS\$1ItemID” | update-expression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '\$1":incr":\$1"N":"1"\$1\$1' | 

## 遊戲設定檔最終結構描述
<a name="data-modeling-schema-gaming-profile-final-schema"></a>

這是最終的結構描述設計。若要將此結構描述設計下載為 JSON 檔案，請參閱 GitHub 上的 [DynamoDB 範例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/GamingPlayerProfiles/GamePlayerProfilesSchema.json)。

**基礎資料表：**

![\[資料表的最終結構描述設計，其中包含先前存取模式實作結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile6-FinalSchema.png)


## 使用 NoSQL Workbench 與此結構描述設計
<a name="data-modeling-schema-gaming-profile-nosql"></a>

您可以將此最終結構描述匯入 [NoSQL Workbench](workbench.md)，這是為 DynamoDB 提供資料建模、資料視覺化，和查詢開發功能的視覺化工具，以進一步探索和編輯新專案。請依照下列步驟以開始使用：

1. 下載 NoSQL Workbench。如需詳細資訊，請參閱[下載 DynamoDB 專用 NoSQL Workbench](workbench.settingup.md)。

1. 下載上面列出的 JSON 結構描述檔案，該檔案已經是 NoSQL Workbench 模型格式。

1. 將 JSON 結構描述檔案匯入到 NoSQL Workbench。如需詳細資訊，請參閱[匯入現有的資料模型](workbench.Modeler.ImportExisting.md)。

1. 一旦您匯入到 NOSQL Workbench 後，便可以編輯資料模型。如需詳細資訊，請參閱[編輯現有的資料模型](workbench.Modeler.Edit.md)。

# DynamoDB 中的投訴管理系統結構描述設計
<a name="data-modeling-complaint-management"></a>

## 投訴管理系統商業使用案例
<a name="data-modeling-schema-complaint-management-use-case"></a>

DynamoDB 是非常適合投訴管理系統 (或聯絡中心) 使用案例的資料庫，因為與其相關聯的大多數存取模式都是採用鍵值型交易查詢。這種情況的典型存取模式會是：
+ 建立和更新投訴
+ 呈報投訴
+ 建立和讀取投訴的評論
+ 取得某一位客戶的所有投訴
+ 取得某一位客服人員的所有評論並取得所有呈報 

某些評論可能包含描述投訴或解決方案的附件。雖然這些全都是鍵值存取模式，但可能會有其他需求，例如，有新評論新增至投訴時傳送通知，或是執行分析查詢，以便了解每週在嚴重性 (或客服人員表現) 方面的投訴分佈情形。與生命週期管理或合規有關的其他需求，可能包括從記錄投訴起算經過三年後封存投訴資料。

## 投訴管理系統架構圖
<a name="data-modeling-schema-complaint-management-ad"></a>

下圖顯示投訴管理系統的架構圖。此圖表顯示投訴管理系統使用的不同 AWS 服務 整合。

![\[與數個 AWS 服務整合以合併工作流程滿足非交易需求。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-1-AD.jpg)


除了稍後將在「DynamoDB 資料建模」一節中討論的鍵值交易存取模式之外，還有三項非交易需求。上面的架構圖可分成以下三個工作流程：

1. 有新評論新增至投訴時傳送通知

1. 對每週資料執行分析查詢

1. 封存超過三年的資料

讓我們進一步深入探討上述每一項。

**有新評論新增至投訴時傳送通知**

我們可以使用下面的工作流程來達成此需求：

![\[調用 Lambda 函式以根據 DynamoDB Streams 記錄變更傳送通知的工作流程。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-2-Workflow1.jpg)


[DynamoDB Streams](Streams.md) 是一種變更資料擷取機制，用來記錄 DynamoDB 資料表上的所有寫入活動。您可以設定 Lambda 函式來觸發部分或全部變更。您可以在 Lambda 觸發程序上設定[事件篩選條件](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)，以篩選掉與使用案例無關的事件。在此範例中，我們只能在有新評論新增時使用篩選條件來觸發 Lambda，並傳送通知至可從 [AWS  Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) 或任何其他憑證存放區提取的相關電子郵件 ID。

**對每週資料執行分析查詢**

DynamoDB 適合用於主要進行線上交易處理 (OLTP) 的工作負載。對於具有分析需求的其他 10-20% 存取模式，可以使用受管的[匯出至 Amazon S3](S3DataExport.HowItWorks.md) 功能將資料匯出到 S3，這樣做不會影響 DynamoDB 資料表上的即時流量。讓我們來了解下面這個工作流程：

![\[定期調用 Lambda 函式以將 DynamoDB 資料儲存在 Amazon S3 儲存貯體的工作流程。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-3-Workflow2.jpg)


[Amazon EventBridge](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is) 可用於 AWS Lambda 按排程觸發 - 它可讓您設定 cron 表達式，以便 Lambda 調用定期進行。Lambda 可以調用 `ExportToS3` API 呼叫並將 DynamoDB 資料儲存在 S3 中。隨後 SQL 引擎 (如 [Amazon Athena](https://docs.aws.amazon.com/athena/latest/ug/what-is)) 就能存取此 S3 資料，以對 DynamoDB 資料執行分析查詢，而不會影響資料表上的即時交易工作負載。依嚴重性層級尋找投訴數目的 Athena 範例查詢如下所示：

```
SELECT Item.severity.S as "Severity", COUNT(Item) as "Count"
FROM "complaint_management"."data"
WHERE NOT Item.severity.S = ''
GROUP BY Item.severity.S ;
```

產生的 Athena 查詢結果如下：

![\[Athena 查詢結果顯示 P3、P2 和 P1 嚴重性等級的投訴數量。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-4-Athena.png)


**封存超過三年的資料**

您可以利用 DynamoDB [存留時間 (TTL)](TTL.md) 功能刪除 DynamoDB 資料表中過時的資料，無需額外費用 (但 2019.11.21 (目前) 版的全域資料表複本除外，其中複寫到其他區域的 TTL 刪除項目會耗用寫入容量)。此資料隨即出現，且可從 DynamoDB Streams 取用並封存至 Amazon S3。此需求的工作流程如下：

![\[使用 TTL 功能和 DynamoDB Streams 在 Amazon S3 儲存貯體封存舊資料的工作流程。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-5-Workflow3.jpg)


## 投訴管理系統實體關係圖
<a name="data-modeling-schema-complaint-management-erd"></a>

這是我們將用於投訴管理系統結構描述設計的實體關係圖 (ERD)。

![\[顯示客戶、投訴、註解和客服等實體的投訴管理系統 ERD。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-6-ERD.jpg)


## 投訴管理系統存取模式
<a name="data-modeling-schema-complaint-management-access-patterns"></a>

這些是我們針對投訴管理結構描述設計考量的存取模式。

1. createComplaint

1. updateComplaint

1. updateSeveritybyComplaintID

1. getComplaintByComplaintID

1. addCommentByComplaintID

1. getAllCommentsByComplaintID

1. getLatestCommentByComplaintID

1. getAComplaintbyCustomerIDAndComplaintID

1. getAllComplaintsByCustomerID

1. escalateComplaintByComplaintID

1. getAllEscalatedComplaints

1. getEscalatedComplaintsByAgentID (從最新到最舊的順序)

1. getCommentsByAgentID (兩個日期之間)

## 投訴管理系統結構描述設計演變
<a name="data-modeling-schema-complaint-management-design-evolution"></a>

由於這是投訴管理系統，因此大多數存取模式都以投訴作為主要實體。高基數的 `ComplaintID` 將可確保資料在基礎分割區中均勻分佈，同時也是我們確定的存取模式最常見的搜尋條件。因此，`ComplaintID` 是此資料集中良好的分割區索引鍵選擇。

**步驟 1：位址存取模式 1 (`createComplaint`)、2 (`updateComplaint`)、3 (`updateSeveritybyComplaintID`) 和 4 (`getComplaintByComplaintID`)**

我們可以使用稱為「中繼資料」(或「AA」) 的通用排序索引鍵來儲存投訴專屬資訊，例如 `CustomerID`、`State`、`Severity` 和 `CreationDate`。我們使用單一操作搭配 `PK=ComplaintID` 和 `SK=“metadata”` 來執行下列操作：

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 用來建立新的投訴

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) 用來更新投訴中繼資料中的嚴重性或其他欄位

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 用來提取投訴的中繼資料

![\[投訴項目的主索引鍵、排序索引鍵和屬性值，例如 customer_id 和嚴重性。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-7-Step1.png)


**步驟 2：位址存取模式 5 (`addCommentByComplaintID`)**

此存取模式須在投訴與投訴的評論之間採用一對多關係模型。我們將在這裡採用[垂直分割](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning)技術來使用排序索引鍵，並建立具有不同類型資料的項目集合。只要查看存取模式 6 (`getAllCommentsByComplaintID`) 和 7 (`getLatestCommentByComplaintID`)，就會了解評論需依時間排序。我們也可以同時接收多個評論，如此就能使用[複合排序索引鍵](data-modeling-blocks.md#data-modeling-blocks-composite)技術將時間和 `CommentID` 附加到排序索引鍵屬性中。

其他處理這類可能發生的評論衝突的選項，包括增加時間戳記的精細度，或是加上一個累加數字作為尾碼，而不要使用 `Comment_ID`。在這種情況下，我們將在對應評論之項目的排序索引鍵值加上首碼「comm\$1」，以實現範圍型操作。

另外還需要確保投訴中繼資料中的 `currentState` 能反映新增新評論的狀態。新增評論可能表示，投訴已指派給客服人員或已解決等情況。為了在投訴中繼資料中綁定新增評論和更新目前狀態，我們將採用全有或全無的方式使用 [TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) API。產生的資料表狀態現在看起來像這樣：

![\[使用複合排序索引鍵將投訴及其註解儲存為一對多關係的資料表。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-8-Step2.png)


讓我們在資料表中新增更多資料，另外也新增 `ComplaintID` 作為有別於 `PK` 的另一個欄位，以便在 `ComplaintID` 需要其他索引時，讓模型能夠因應未來需要。另請注意，某些評論可能包含附件，我們會將這些附件儲存在 Amazon Simple Storage Service 中，並且只會在 DynamoDB 中維護其參考或 URL。最佳實務是盡可能保持交易資料庫精簡，以最佳化成本和效能。資料現在看起來像這樣：

![\[資料表包含投訴中繼資料，以及與每項投訴相關聯的所有評論資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-9-Step3.png)


**步驟 3：位址存取模式 6 (`getAllCommentsByComplaintID`) 和 7 (`getLatestCommentByComplaintID`)**

若要取得投訴的所有評論，我們可以在排序索引鍵上使用 [`query`](Query.md) 操作搭配 `begins_with` 條件。與其耗用額外的讀取容量來讀取中繼資料項目，隨後因篩選相關結果而造成額外負荷，倒不如使用項這樣的排序索引鍵條件幫助我們只讀取所要的內容。例如使用 `PK=Complaint123` 及 `SK` begins\$1with `comm#` 進行查詢操作，會傳回下列內容並略過中繼資料項目：

![\[使用僅顯示投訴評論的排序索引鍵條件的查詢操作結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-10-Step4.png)


既然我們在模式 7 (`getLatestCommentByComplaintID`) 中需要投訴的最新評論，那麼就使用兩個額外的查詢參數：

1. `ScanIndexForward` 應設定為 False，才能以遞減順序排序結果

1. `Limit` 應設定為 1，才能取得最新 (單獨一個) 評論

類似存取模式 6 (`getAllCommentsByComplaintID`)，我們使用 `begins_with` `comm#` 作為排序索引鍵條件以略過中繼資料項目。現在，您可以使用查詢操作搭配 `PK=Complaint123` 和 `SK=begins_with comm#`、`ScanIndexForward=False`、`Limit` 1，對此設計執行存取模式 7。結果會傳回以下目標項目：

![\[使用排序索引鍵條件取得投訴最後評論的查詢操作結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-11-Step5.png)


讓我們新增更多虛設資料到資料表中。

![\[包含虛擬資料的資料表，以取得所收到投訴的最新評論。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-12-Step6.png)


**步驟 4：位址存取模式 8 (`getAComplaintbyCustomerIDAndComplaintID`) 和 9 (`getAllComplaintsByCustomerID`)**

存取模式 8 (`getAComplaintbyCustomerIDAndComplaintID`) 和 9 (`getAllComplaintsByCustomerID`) 導入了新的搜尋條件：`CustomerID`。若要從現有資料表提取它，則須使用昂貴的 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) 來讀取所有資料，然後篩選相關項目以找出所指的 `CustomerID`。我們只要建立[全域次要索引 (GSI)](GSI.md) 並搭配 `CustomerID` 作為分割區索引鍵，就能讓這項搜尋更有效率。務必記住客戶與投訴之間的一對多關係以及存取模式 9 (`getAllComplaintsByCustomerID`)，`ComplaintID` 會是排序索引鍵的合適選擇。

GSI 中的資料如下所示：

![\[具有一對多關係模型的 GSI，以取得特定 CustomerID 的所有投訴。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-13-Step4-GSI.png)


 此 GSI 上存取模式 8 (`getAComplaintbyCustomerIDAndComplaintID`) 的範例查詢會是：`customer_id=custXYZ`、`sort key=Complaint1321`。結果會是：

![\[GSI 上的查詢操作結果，以取得特定客戶提出的特定投訴資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-14-Step4-8.png)


若要針對存取模式 9 (`getAllComplaintsByCustomerID`) 取得某一個客戶的所有投訴，GSI 上的查詢會是：`customer_id=custXYZ` 作為分割區索引鍵條件。結果會是：

![\[使用分割區索引鍵條件的查詢操作結果，以取得特定客戶的所有投訴。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-15-Step4-9.png)


**步驟 5：位址存取模式 10 (`escalateComplaintByComplaintID`)**

此存取將介紹呈報相關層面。若要呈報投訴，我們可以使用 `UpdateItem` 將 `escalated_to` 和 `escalation_time` 等屬性新增至現有投訴中繼資料項目中。DynamoDB 提供了靈活的結構描述設計，這表示非索引鍵屬性集可以合併在一起，也可以分散到不同的項目中。如需範例，請參閱下列內容：

`UpdateItem with PK=Complaint1444, SK=metadata`

![\[使用 UpdateItem API 操作更新投訴中繼資料的結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-16-Step5.png)


**步驟 6：位址存取模式 11 (`getAllEscalatedComplaints`) 和 12 (`getEscalatedComplaintsByAgentID`)**

預計在整個資料集中，只有少數投訴會呈報。因此，在呈報相關屬性上建立索引將能實現高效率的查詢與符合成本效益的 GSI 儲存。我們可以利用[稀疏索引](data-modeling-blocks.md#data-modeling-blocks-sparse-index)技術來實現這個目標。具有分割區索引鍵 `escalated_to` 及排序索引鍵 `escalation_time` 的 GSI 如下所示：

![\[使用 escalated_to 和 escalation_time 等呈報相關屬性的 GSI 設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-17-Step6.png)


若要針對存取模式 11 (`getAllEscalatedComplaints`) 取得所有呈報的投訴，只要掃描此 GSI 即可。請注意，由於 GSI 的大小，此掃描將會是高效能且具有成本效益。若要取得特定客服人員的 (存取模式 12 (`getEscalatedComplaintsByAgentID`)) 的已呈報投訴，分割區索引鍵會是 `escalated_to=agentID`，且我們會將 `ScanIndexForward` 設定為 `False`，以便從最新到最舊排序。

**步驟 7：位址存取模式 13 (`getCommentsByAgentID`)**

對於最後一個存取模式，我們需要以新的維度執行查詢：`AgentID`。另外還需要依時間進行排序來讀取兩個日期之間的評論，因此我們會建立一個 GSI，並以 `agent_id` 作為分割區索引鍵及 `comm_date` 作為排序索引鍵。此 GSI 中的資料如下所示：

![\[GSI 設計依使用評論日期排序的特定客服查詢評論。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-18.png)


此 GSI 的範例查詢會是 `partition key agentID=AgentA` 和 `sort key=comm_date between (2023-04-30T12:30:00, 2023-05-01T09:00:00)`，其結果為：

![\[在 GSI 上使用分割區索引鍵和排序索引鍵的查詢結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-19.png)


下表摘要整理了所有存取模式，以及結構描述設計處理這些模式的方式：


| 存取模式 | 基礎資料表/GSI/LSI | 作業 | 分割區索引鍵值 | 排序索引鍵值 | 其他條件/篩選條件 | 
| --- | --- | --- | --- | --- | --- | 
| createComplaint | 基礎資料表 | PutItem | PK=complaint\$1id | SK=metadata |  | 
| updateComplaint | 基礎資料表 | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| updateSeveritybyComplaintID | 基礎資料表 | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| getComplaintByComplaintID | 基礎資料表 | GetItem | PK=complaint\$1id | SK=metadata |  | 
| addCommentByComplaintID | 基礎資料表 | TransactWriteItems | PK=complaint\$1id | SK=metadata, SK=comm\$1comm\$1date\$1comm\$1id |  | 
| getAllCommentsByComplaintID | 基礎資料表 | Query | PK=complaint\$1id | SK begins\$1with "comm\$1" |  | 
| getLatestCommentByComplaintID | 基礎資料表 | Query | PK=complaint\$1id | SK begins\$1with "comm\$1" | scan\$1index\$1forward=False, Limit 1 | 
| getAComplaintbyCustomerIDAndComplaintID | Customer\$1complaint\$1GSI | Query | customer\$1id=customer\$1id | complaint\$1id = complaint\$1id |  | 
| getAllComplaintsByCustomerID | Customer\$1complaint\$1GSI | Query | customer\$1id=customer\$1id | N/A |  | 
| escalateComplaintByComplaintID | 基礎資料表 | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| getAllEscalatedComplaints | Escalations\$1GSI | Scan | N/A | N/A |  | 
| getEscalatedComplaintsByAgentID (從最新到最舊的順序) | Escalations\$1GSI | Query | escalated\$1to=agent\$1id | N/A | scan\$1index\$1forward=False | 
| getCommentsByAgentID (兩個日期之間) | Agents\$1Comments\$1GSI | Query | agent\$1id=agent\$1id | SK between (date1, date2) |  | 

## 投訴管理系統最終結構描述
<a name="data-modeling-schema-complaint-management-final-schema"></a>

以下是最終結構描述設計。若要將此結構描述設計下載為 JSON 檔案，請參閱 GitHub 上的 [DynamoDB 範例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ComplainManagement/ComplaintManagementSchema.json)。

**基礎資料表**

![\[包含投訴中繼資料的基礎資料表設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-20-Complaint_management_system.png)


**Customer\$1Complaint\$1GSI**

![\[顯示特定客戶投訴的 GSI 設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-21-Customer_Complaint_GSI.png)


**Escalations\$1GSI**

![\[顯示呈報相關屬性的 GSI 設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-22-Escalations_GSI.png)


**Agents\$1Comments\$1GSI**

![\[顯示特定客服所做評論的 GSI 設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-23-Comments_GSI.png)


## 使用 NoSQL Workbench 與此結構描述設計
<a name="data-modeling-schema-complaint-management-nosql"></a>

您可以將此最終結構描述匯入 [NoSQL Workbench](workbench.md)，這是為 DynamoDB 提供資料建模、資料視覺化，和查詢開發功能的視覺化工具，以進一步探索和編輯新專案。請依照下列步驟以開始使用：

1. 下載 NoSQL Workbench。如需詳細資訊，請參閱[下載 DynamoDB 專用 NoSQL Workbench](workbench.settingup.md)。

1. 下載上面列出的 JSON 結構描述檔案，該檔案已經是 NoSQL Workbench 模型格式。

1. 將 JSON 結構描述檔案匯入到 NoSQL Workbench。如需詳細資訊，請參閱[匯入現有的資料模型](workbench.Modeler.ImportExisting.md)。

1. 一旦您匯入到 NOSQL Workbench 後，便可以編輯資料模型。如需詳細資訊，請參閱[編輯現有的資料模型](workbench.Modeler.Edit.md)。

# DynamoDB 中的週期性付款結構描述設計
<a name="data-modeling-schema-recurring-payments"></a>

## 週期性付款商業使用案例
<a name="data-modeling-schema-recurring-payments-use-case"></a>

此使用案例將說明如何使用 DynamoDB 實作週期性付款系統。資料模型具有以下實體：*帳戶*、*訂閱*及*收據*。我們的使用案例包括以下細節：
+ 每個*帳戶*可以有多個*訂閱*
+ 需要處理下一次付款時，*訂閱*會有 `NextPaymentDate`，傳送電子郵件提醒給客戶時，則會有 `NextReminderDate`
+ 有一個代表*訂閱*的項目會儲存並在付款處理完成時更新 (平均項目大小約為 1KB，輸送量取決於*帳戶*和*訂閱*的數目)
+ *付款*處理器還會在過程中建立一張*收據*並儲存在資料表中，而且會使用 [TTL](TTL.md) 屬性將它設定為在經過一段時間後過期。

## 週期性付款實體關係圖
<a name="data-modeling-schema-recurring-payments-erd"></a>

這是我們將用於週期性付款系統結構描述設計的實體關係圖 (ERD)。

![\[顯示實體的週期性付款系統 ERD：帳戶、訂閱和收據。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-1-ERD.png)


## 週期性付款系統存取模式
<a name="data-modeling-schema-recurring-payments-access-patterns"></a>

這些是我們針對週期性付款系統結構描述設計考量的存取模式。

1. `createSubscription`

1. `createReceipt`

1. `updateSubscription`

1. `getDueRemindersByDate`

1. `getDuePaymentsByDate`

1. `getSubscriptionsByAccount`

1. `getReceiptsByAccount`

## 週期性付款結構描述設計
<a name="data-modeling-schema-recurring-payments-design-evolution"></a>

通用名稱 `PK` 和 `SK` 用於金鑰屬性，以允許在相同資料表中儲存不同類型的實體，例如帳戶、訂閱和收據實體。使用者首先建立訂閱，這代表使用者同意在每個月的同一天支付一定金額來換取使用產品。使用者可以選擇要在每個月的哪一天處理付款。另外還會在處理付款之前傳送提醒。應用程式的運作方式是每天執行兩個批次任務：一個批次任務傳送當天應送出的提醒，另一個批次任務則處理當天應付的任何款項。

**步驟 1：位址存取模式 1 (`createSubscription`)**

存取模式 1 (`createSubscription`) 用於初次建立訂閱，並設定包括 `SKU`、`NextPaymentDate`、`NextReminderDate` 和 `PaymentDetails` 等詳細資訊。此步驟僅顯示具有一個訂閱的一個帳戶的資料表狀態。項目集合中可以有多個訂閱，因此這是一對多關係。

![\[顯示帳戶訂閱詳細資訊的資料表設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-2-Step1.png)


**步驟 2：位址存取模式 2 (`createReceipt`) 和 3 (`updateSubscription`)**

存取模式 2 (`createReceipt`) 用於建立收據項目。每個月處理付款後，付款處理器會將收據寫回基礎資料表。項目集合中可以有多個收據，因此這是一對多關係。付款處理器也會更新訂閱項目 (存取模式 3 (`updateSubscription`)) 以更新下個月的 `NextReminderDate` 或 `NextPaymentDate`。

![\[收據詳細資訊和訂閱項目更新，以顯示下一個訂閱提醒日期。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-3-Step2.png)


**步驟 3：位址存取模式 4 (`getDueRemindersByDate`)**

應用程式會分批處理當天的付款提醒。因此，應用程式需要存取不同維度的訂閱：日期，而非帳戶。這是一個很好的[全域次要索引 (GSI)](GSI.md) 使用案例。在此步驟中，我們會新增索引 `GSI-1`，它會使用 `NextReminderDate` 作為 GSI 分割區索引鍵。我們不需要複寫所有項目。這個 GSI 是[稀疏索引](data-modeling-blocks.md#data-modeling-blocks-sparse-index)，因此不會複寫收據項目。我們也不需要對應所有的屬性，只需要包括屬性的子集。下方影像顯示了 `GSI-1` 的結構描述，它提供了應用程式傳送提醒電子郵件所需的資訊。

![\[包含電子郵件地址等詳細資訊的 GSI-1 結構描述，應用程式需要傳送提醒電子郵件。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-4-Step3.png)


**步驟 4：位址存取模式 5 (`getDuePaymentsByDate`)**

應用程式會分批處理當天的付款，採取與處理提醒相同的方式。我們會在此步驟中新增 `GSI-2`，它會使用 `NextPaymentDate` 作為 GSI 分割區索引鍵。我們不需要複寫所有項目。這個 GSI 是稀疏索引，因為不會複寫收據項目。下方影像顯示了 `GSI-2` 的結構描述。

![\[GSI-2 結構描述包含處理付款的詳細資訊。NextPaymentDate 是 GSI-2 的分割區索引鍵。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-5-Step4.png)


**步驟 5：位址存取模式 6 (`getSubscriptionsByAccount`) 和 7 (`getReceiptsByAccount`)**

應用程式會在基礎資料表上使用[查詢](Query.md)來鎖定帳戶識別碼 (`PK`) 為目標，以擷取帳戶的所有訂閱，並使用範圍運算子取得 `SK` 開頭為「SUB\$1」的所有項目。應用程式也可以使用相同的查詢結構來擷取所有收據，方法是使用範圍運算子來取得 `SK` 開頭為「REC\$1」的所有項目。這樣就能滿足存取模式 6 (`getSubscriptionsByAccount`) 和 7 (`getReceiptsByAccount`)。應用程式使用這些存取模式讓使用者看見過去六個月內自己目前的訂閱及過去的收據。在此步驟中，我們不會變更資料表結構描述，而且可以在下面看到我們如何單獨鎖定存取模式 6 (`getSubscriptionsByAccount`) 中的訂閱項目。

![\[基礎資料表的查詢操作結果。其中會顯示特定帳戶的訂閱。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-6-Step5.png)


下表摘要整理了所有存取模式，以及結構描述設計處理這些模式的方式：


| 存取模式 | 基礎資料表/GSI/LSI | 作業 | 分割區索引鍵值 | 排序索引鍵值 | 
| --- | --- | --- | --- | --- | 
| createSubscription | 基礎資料表 | PutItem | ACC\$1account\$1id | SUB\$1<SUBID>\$1SKU<SKUID> | 
| createReceipt | 基礎資料表 | PutItem | ACC\$1account\$1id | REC\$1<RecieptDate>\$1SKU<SKUID> | 
| updateSubscription | 基礎資料表 | UpdateItem | ACC\$1account\$1id | SUB\$1<SUBID>\$1SKU<SKUID> | 
| getDueRemindersByDate | GSI-1 | Query | <NextReminderDate> |  | 
| getDuePaymentsByDate | GSI-2 | Query | <NextPaymentDate> |  | 
| getSubscriptionsByAccount | 基礎資料表 | Query | ACC\$1account\$1id | SK begins\$1with “SUB\$1” | 
| getReceiptsByAccount | 基礎資料表 | Query | ACC\$1account\$1id | SK begins\$1with “REC\$1” | 

## 週期性付款最終結構描述
<a name="data-modeling-schema-recurring-payments-final-schema"></a>

以下是最終結構描述設計。若要將此結構描述設計下載為 JSON 檔案，請參閱 GitHub 上的 [DynamoDB 範例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ReocurringPayments/ReocurringPaymentsSchema.json)。

**基礎資料表**

![\[顯示帳戶資訊及其訂閱和收據詳細資訊的基礎資料表設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-7-Base.png)


**GSI-1**

![\[包含電子郵件地址和 NextPaymentDate 等訂閱詳細資訊的 GSI-1 結構描述。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-8-GSI1.png)


**GSI-2**

![\[包含 PaymentAmount 和 PaymentDay 等付款詳細資訊的 GSI-2 結構描述。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-9-GSI2.png)


## 使用 NoSQL Workbench 與此結構描述設計
<a name="data-modeling-schema-recurring-payments-nosql"></a>

您可以將此最終結構描述匯入 [NoSQL Workbench](workbench.md)，這是為 DynamoDB 提供資料建模、資料視覺化，和查詢開發功能的視覺化工具，以進一步探索和編輯新專案。請依照下列步驟以開始使用：

1. 下載 NoSQL Workbench。如需詳細資訊，請參閱[下載 DynamoDB 專用 NoSQL Workbench](workbench.settingup.md)。

1. 下載上面列出的 JSON 結構描述檔案，該檔案已經是 NoSQL Workbench 模型格式。

1. 將 JSON 結構描述檔案匯入到 NoSQL Workbench。如需詳細資訊，請參閱[匯入現有的資料模型](workbench.Modeler.ImportExisting.md)。

1. 一旦您匯入到 NOSQL Workbench 後，便可以編輯資料模型。如需詳細資訊，請參閱[編輯現有的資料模型](workbench.Modeler.Edit.md)。

# 監控 DynamoDB 中的裝置狀態更新
<a name="data-modeling-device-status"></a>

本使用案例將說明如何使用 DynamoDB 監控 DynamoDB 中的裝置狀態更新 (或裝置狀態變更)。

## 使用案例
<a name="data-modeling-schema-device-status-use-case"></a>

在 IoT 使用案例 (例如智慧工廠) 中，許多裝置需要由作業員監控，並定期將其狀態或日誌傳送至監控系統。當裝置發生問題時，裝置的狀態會從*正常*轉為*警告*。根據裝置中異常行為的嚴重性和類型，會有不同的日誌層級或狀態。然後，系統會指派作業員檢查裝置，並在需要時將問題呈報給主管。

此系統的典型存取模式包括：
+ 建立裝置的日誌項目
+ 取得特定裝置狀態的所有日誌，並顯示最新日誌
+ 取得兩個日期之間，給指定作業員的所有日誌
+ 取得呈報給指定主管的所有日誌
+ 取得呈報給指定主管，並包含特定裝置狀態的所有日誌
+ 取得呈報給指定主管，並包含特定日期之特定裝置狀態的所有日誌

## 實體關係圖
<a name="data-modeling-schema-device-status-erd"></a>

這是用於監控裝置狀態更新實體關係圖 (ERD)。

![\[裝置狀態更新的 ERD。其中顯示的實體：裝置和運算子。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-1-ERD.jpg)


## 存取模式
<a name="data-modeling-schema-device-status-access-patterns"></a>

須使用此存取模式監控裝置狀態更新。

1. `createLogEntryForSpecificDevice`

1. `getLogsForSpecificDevice`

1. `getWarningLogsForSpecificDevice`

1. `getLogsForOperatorBetweenTwoDates`

1. `getEscalatedLogsForSupervisor`

1. `getEscalatedLogsWithSpecificStatusForSupervisor`

1. `getEscalatedLogsWithSpecificStatusForSupervisorForDate`

## 架構設計演進
<a name="data-modeling-schema-device-status-design-evolution"></a>

**步驟 1：位址存取模式 1 (`createLogEntryForSpecificDevice`) 和 2 (`getLogsForSpecificDevice`)**

裝置追蹤系統的擴展單位為個別裝置。在這個系統中，`deviceID` 會唯一識別裝置，進一步讓 `deviceID` 成為分割區索引鍵的理想候選者。每個設備會定期向追蹤系統發送資訊 (例如每五分鐘左右)，而這類排序會依日期為邏輯排序準則，因此成為排序索引鍵。此使用案例中的範例資料看起來應會與以下內容相似：

![\[儲存多個裝置狀態的資料表。DeviceID 是主索引鍵，而狀態更新日期是排序索引鍵。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-2-Step1.png)


若要擷取特定裝置的日誌項目，可以使用分割區索引鍵 `DeviceID="d#12345"` 執行[查詢](Query.md)操作。

**步驟 2：位址存取模式 3 (`getWarningLogsForSpecificDevice`)**

由於 `State` 是非索引鍵屬性，必須使用[篩選條件表達式](Query.FilterExpression.md)，才可利用目前的結構描述處理存取模式 3。在 DynamoDB 中，使用索引鍵條件表達式讀取資料後，才會套用篩選條件表達式。例如若想針對 `d#12345` 擷取警告日誌，利用分割區索引鍵 `DeviceID="d#12345"` 進行的查詢操作會讀取上表中的四個項目，然後篩選掉非處於*警告*狀態的項目。這種方法無法有效地大規模進行。如果排除項目的比例較低或不常執行查詢，則篩選條件表達式是排除查詢項目的好方法。但是，我們可以持續改善資料表設計，以便更有效率地從資料表中擷取大量項目，並篩選掉大部分的項目。

若想改變存取模式的處理方法，可以透過[複合排序索引鍵](data-modeling-blocks.md#data-modeling-blocks-composite)。您可以從排序索引鍵已改為 `State#Date` 的 [DeviceStateLog\$13.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_3.json) 匯入範例資料。此排序索引鍵結合了 `State`、`#` 以及 `Date` 屬性。此範例中，`#` 用來作為分隔符。資料現在看起來會像這樣：

![\[使用複合排序索引鍵 State#Date 擷取的裝置 d#12345 狀態更新資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-3-Step2.png)


若僅想擷取裝置的警告日誌，可利用此結構描述更切合查詢。查詢的索引鍵條件使用分割區索引鍵 `DeviceID="d#12345"` 與排序索引鍵 `State#Date begins_with “WARNING”`。此查詢只會讀取三個與*警告*狀態相關的項目。

**步驟 3：位址存取模式 4 (`getLogsForOperatorBetweenTwoDates`)**

您可以匯入 [DeviceStateLog\$14.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_4.json)，其中 `Operator` 屬性已新增至具有範例資料的 `DeviceStateLog` 資料表。

![\[DeviceStateLog 資料表設計具有運算子屬性，可取得特定日期之間的運算子日誌。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-4-Step3.png)


由於 `Operator` 目前不是分割區索引鍵，因此無法根據以 `OperatorID` 為基礎的資料表執行直接鍵值對查詢。我們必須使用 `OperatorID` 上的全域次要索引，建立一個新的[項目集合](WorkingWithItemCollections.md)。存取模式必須根據日期進行查詢，因此日期是[全域次要索引 (GSI)](GSI.md) 的排序索引鍵屬性。這就是 GSI 現在的樣子：

![\[使用 OperatorID 和 Date 做為分割區索引鍵和排序索引鍵的 GSI 設計，以取得特定運算子的日誌。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-5-Step3.png)


針對存取模式 4 (`getLogsForOperatorBetweenTwoDates`)，您可以使用分割區索引鍵 `OperatorID=Liz` 查詢此 GSI，以及 `2020-04-11T05:58:00` 和 `2020-04-24T14:50:00` 之間的排序索引鍵 `Date`。

![\[使用 OperatorID 和 Date 查詢 GSI，以取得兩個日期之間的運算子日誌。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-6-GSI1_1.png)


**步驟 4：位址存取模式 5 (`getEscalatedLogsForSupervisor`)、6 (`getEscalatedLogsWithSpecificStatusForSupervisor`) 和 7 (`getEscalatedLogsWithSpecificStatusForSupervisorForDate`)**

我們可以利用[疏鬆索引](data-modeling-blocks.md#data-modeling-blocks-sparse-index)處理這些存取模式。

全域次要索引依預設是疏鬆的，因此只有基礎資料表中包含主索引鍵屬性的項目，才會實際出現在索引中。針對要建模的存取模式，這是另一種排除無關項目的方式。

您可以匯入 [DeviceStateLog\$16.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_6.json)，其中 `EscalatedTo` 屬性已新增至具有範例資料的 `DeviceStateLog` 資料表。如前所述，並非所有日誌都會呈報給主管。

![\[具有 EscalatedTo 屬性的 GSI 設計，以取得主管的所有呈報日誌。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-7-Step4.png)


您現在可以建立分割區索引鍵為 `EscalatedTo`、排序索引鍵為 `State#Date` 的新 GSI。請注意，只有當項目同時具有 `EscalatedTo` 和 `State#Date` 屬性時，才會顯示在索引中。

![\[取得具有 EscalatedTo 和 State#Date 屬性等所有項目的 GSI 設計。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-8-Step4.png)


其餘的存取模式摘要如下：

    針對存取模式 5 (getEscalatedLogsForSupervisor)，您可以使用分割區索引鍵 EscalatedTo="Sara" 在呈報 GSI 上執行查詢。   針對存取模式 6 (getEscalatedLogsWithSpecificStatusForSupervisor)，您可以使用分割區索引鍵 EscalatedTo="Sara"，與排序索引鍵 State\$1Date begins\$1with “WARNING”，在呈報 GSI 上執行查詢。   針對存取模式 7 (getEscalatedLogsWithSpecificStatusForSupervisorForDate)，您可以使用分割區索引鍵 EscalatedTo="Sara"，與排序索引鍵 State\$1Date begins\$1with “WARNING4\$12020-04-27”，在呈報 GSI 上執行查詢。    

下表摘要整理了所有存取模式，以及結構描述設計處理這些模式的方式：


| 存取模式 | 基礎資料表/GSI/LSI | 作業 | 分割區索引鍵值 | 排序索引鍵值 | 其他條件/篩選條件 | 
| --- | --- | --- | --- | --- | --- | 
| createLogEntryForSpecificDevice | 基礎資料表 | PutItem | DeviceID=deviceId | State\$1Date=state\$1date |  | 
| getLogsForSpecificDevice | 基礎資料表 | Query | DeviceID=deviceId | State\$1Date begins\$1with "state1\$1" | ScanIndexForward = False | 
| getWarningLogsForSpecificDevice | 基礎資料表 | Query | DeviceID=deviceId | State\$1Date begins\$1with "WARNING" |  | 
| getLogsForOperatorBetweenTwoDates | GSI-1 | Query | Operator=operatorName | Date between date1 and date2 |  | 
| getEscalatedLogsForSupervisor | GSI-2 | Query | EscalatedTo=supervisorName |  |  | 
| getEscalatedLogsWithSpecificStatusForSupervisor | GSI-2 | Query | EscalatedTo=supervisorName | State\$1Date begins\$1with "state1\$1" |  | 
| getEscalatedLogsWithSpecificStatusForSupervisorForDate | GSI-2 | Query | EscalatedTo=supervisorName | State\$1Date begins\$1with "state1\$1date1" |  | 

## 最終結構描述
<a name="data-modeling-schema-device-status-final-schema"></a>

以下是最終結構描述設計。若要將此結構描述設計下載為 JSON 檔案，請參閱 GitHub 上的 [DynamoDB 範例](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples)。

**基礎資料表**

![\[具有裝置狀態中繼資料的基礎資料表設計，例如 Device ID、State 和 Date。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-9-Table.png)


**GSI-1**

![\[GSI-1 設計。其中會顯示主索引鍵和屬性：DeviceID、State#Date 和 State。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-10-GSI1.png)


**GSI-2**

![\[GSI-2 設計。其中會顯示主索引鍵和屬性：DeviceID、Operator、Date 和 State。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-11-GSI2.png)


## 使用 NoSQL Workbench 與此結構描述設計
<a name="data-modeling-schema-device-status-nosql"></a>

您可以將此最終結構描述匯入 [NoSQL Workbench](workbench.md)，這是為 DynamoDB 提供資料建模、資料視覺化，和查詢開發功能的視覺化工具，以進一步探索和編輯新專案。請依照下列步驟以開始使用：

1. 下載 NoSQL Workbench。如需詳細資訊，請參閱[下載 DynamoDB 專用 NoSQL Workbench](workbench.settingup.md)。

1. 下載上面列出的 JSON 結構描述檔案，該檔案已經是 NoSQL Workbench 模型格式。

1. 將 JSON 結構描述檔案匯入到 NoSQL Workbench。如需詳細資訊，請參閱[匯入現有的資料模型](workbench.Modeler.ImportExisting.md)。

1. 一旦您匯入到 NOSQL Workbench 後，便可以編輯資料模型。如需詳細資訊，請參閱[編輯現有的資料模型](workbench.Modeler.Edit.md)。

# 使用 DynamoDB 做為線上商店的資料存放區
<a name="data-modeling-online-shop"></a>

此使用案例說明如何將 DynamoDB 做為線上商店 (或電子商店) 的資料存放區。

## 使用案例
<a name="data-modeling-schema-online-shop"></a>

使用者可在線上商店瀏覽各種產品，並進行購買。結帳時客戶可以使用折扣碼或禮品卡付款，並用信用卡支付剩餘金額。從數個倉庫中找出購買產品後，便會運送到客戶提供的地址。線上商店存放區的典型存取模式包括：
+ 取得具指定 CustomerID 的客戶
+ 取得具指定 ProductID 的產品
+ 取得具指定 WarehouseID 的倉庫
+ 透過 ProductID 取得所有倉庫的產品庫存
+ 取得具指定 OrderID 的訂單
+ 取得具指定 OrderID 的所有產品
+ 取得具指定 OrderID 的發票
+ 取得具指定 OrderID 的所有運送貨物
+ 取得指定日期範圍內，具指定 ProductID 的所有訂單
+ 取得具指定 InvoiceID 的發票
+ 取得具指定 InvoiceID 的所有付款
+ 取得具指定 ShipmentID 的所有運送詳細資訊
+ 取得具指定 WarehouseID 的所有運送貨物
+ 取得具指定 WarehouseID 的所有產品庫存
+ 取得指定日期範圍內，具指定 CustomerID 的所有發票
+ 取得指定日期範圍內，由指定 CustomerID 訂購的所有產品

## 實體關係圖
<a name="data-modeling-schema-online-shop-erd"></a>

針對如何使用 DynamoDB 做為線上商店的資料存放區，這是用於建模的實體關係圖 (ERD)。

![\[線上儲存資料模型的 ERD 與實體，例如產品、訂單、付款和客戶。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-1-ERD.png)


## 存取模式
<a name="data-modeling-schema-online-shop-access-patterns"></a>

針對如何使用 DynamoDB 做為線上商店的資料存放區，這些是可採用的存取模式。

1. `getCustomerByCustomerId`

1. `getProductByProductId`

1. `getWarehouseByWarehouseId`

1. `getProductInventoryByProductId`

1. `getOrderDetailsByOrderId`

1. `getProductByOrderId`

1. `getInvoiceByOrderId`

1. `getShipmentByOrderId`

1. `getOrderByProductIdForDateRange`

1. `getInvoiceByInvoiceId`

1. `getPaymentByInvoiceId`

1. `getShipmentDetailsByShipmentId`

1. `getShipmentByWarehouseId`

1. `getProductInventoryByWarehouseId`

1. `getInvoiceByCustomerIdForDateRange`

1. `getProductsByCustomerIdForDateRange`

## 架構設計演進
<a name="data-modeling-schema-online-shop-design-evolution"></a>

使用 [DynamoDB 專用 NoSQL Workbench](workbench.md)，匯入 [AnOnlineShop\$11.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_1.json) 以建立命名為 `AnOnlineShop` 的新資料模型，和一個命名為 `OnlineShop` 的新資料表。請注意，我們將分割區索引鍵和排序索引鍵命名為通用名稱 `PK` 和 `SK`，藉此可在同一個資料表中儲存不同類型的實體。

**步驟 1：位址存取模式 1 (`getCustomerByCustomerId`)**

匯入 [AnOnlineShop\$12.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_2.json) 處理存取模式 1 (`getCustomerByCustomerId`)。某些實體與其他實體並無關聯，因此我們將針對這些實體使用相同的 `PK` 和 `SK` 值。在範例資料中，請注意索引鍵字首為 `c#`，以和稍後會從其他實體新增的 `customerId` 進行區別。也請於其他實體重複執行此做法。

若要處理此存取模式，可以利用 `PK=customerId` 和 `SK=customerId` 執行 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 操作。

**步驟 2：位址存取模式 2 (`getProductByProductId`)**

匯入 [AnOnlineShop\$13.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_3.json)，針對 `product` 實體處理存取模式 2 (`getProductByProductId`)。產品實體字首為 `p#`，並且已使用相同的排序索引鍵屬性來儲存 `customerID` 與 `productID`。透過通用命名和[垂直分割](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning)建立項目集合，可設計有效的單一資料表。

若要處理此存取模式，可以利用 `PK=productId` 和 `SK=productId` 執行 `GetItem` 操作。

**步驟 3：位址存取模式 3 (`getWarehouseByWarehouseId`)**

匯入 [AnOnlineShop\$14.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_4.json)，針對 `getWarehouseByWarehouseId` 實體處理存取模式 3 (`warehouse`)。目前我們已將 `customer`、`product` 與 `warehouse` 實體加入同一個資料表，且這些實體均使用字首和 `EntityType` 屬性。類型屬性 (或字首命名) 能提升模型的可讀性。如果只單純將不同實體的英數 ID 儲存在同一屬性中，閱讀上會相當困難。若少了識別符，就會難以分辨不同實體。

若要處理此存取模式，可以利用 `PK=warehouseId` 和 `SK=warehouseId` 執行 `GetItem` 操作。

**基礎資料表：**

![\[DynamoDB 資料表設計包含字首和 EntityType，可依其 ID 取得倉儲資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-2-Step3.png)


**步驟 4：位址存取模式 4 (`getProductInventoryByProductId`)**

匯入 [AnOnlineShop\$15.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_5.json)，處理存取模式 4 (`getProductInventoryByProductId`)。而 `warehouseItem` 實體會用於追蹤每個倉庫中的產品數量。當倉庫新增或移除產品時，通常會更新此項目。正如在 ERD 所看到的，`product` 和 `warehouse` 之間存在多對多關係。在此處，從 `product` 至 `warehouse` 的一對多關係已建立模型 `warehouseItem`。接著，從 `product` 至 `warehouse` 的一對多關係也會進行建模。

可透過查詢 `PK=ProductId` 和 `SK begins_with “w#“` 處理存取模式 4。

關於 `begins_with()` 和其他可套用至排序索引鍵的表達式，詳細資訊請參閱[索引鍵條件表達式](Query.KeyConditionExpressions.md)。

**基礎資料表：**

![\[用於查詢 ProductID 和 warehouseId 的資料表設計，以追蹤特定倉儲中的產品庫存。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-3-Step4.png)


**步驟 5：位址存取模式 5 (`getOrderDetailsByOrderId`) 和 6 (`getProductByOrderId`)**

透過匯入 [AnOnlineShop\$16.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_6.json)，將更多 `customer`、`product` 和 `warehouse` 項目新增至資料表。然後，匯入 [AnOnlineShop\$17.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_7.json)，針對 `order` 建立項目集合以處理存取模式 5 (`getOrderDetailsByOrderId`) 和 6 (`getProductByOrderId`)。您可以看到 `order` 和 `product` 之間的一對多關係已建模為 orderItem 實體。

若想處理存取模式 5 (`getOrderDetailsByOrderId`)，請透過 `PK=orderId` 查詢資料表，了解與訂單相關的所有資訊，包括 `customerId` 和已訂購產品。

**基礎資料表：**

![\[使用 orderId 查詢的資料表設計，以取得所有已訂購產品的相關資訊。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-4-Step5.png)


若想處理存取模式 6 (`getProductByOrderId`)，僅能以 `order` 讀取產品，此方法可透過利用 `PK=orderId` 和 `SK begins_with “p#”` 查詢資料表完成。

**基礎資料表：**

![\[使用 orderId 和 productId 查詢的資料表設計，以取得訂單中的產品。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-5-Step5.png)


**步驟 6：位址存取模式 7 (`getInvoiceByOrderId`)**

匯入 [AnOnlineShop\$18.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_8.json)，新增 `invoice` 實體到*訂單*項目集合，以處理存取模式 7 (`getInvoiceByOrderId`)。若要處理此存取模式，可以利用 `PK=orderId` 和 `SK begins_with “i#”` 執行查詢操作。

**基礎資料表：**

![\[訂單項目集合中包含發票實體的資料表設計，以依據 orderId 取得發票。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-6-Step6.png)


**步驟 7：位址存取模式 8 (`getShipmentByOrderId`)**

匯入 [AnOnlineShop\$19.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_9.json)，新增 `shipment` 實體到*訂單*項目集合，以處理存取模式 8 (`getShipmentByOrderId`)。我們可透過在單一資料表設計中新增更多實體類型，擴展同一個的垂直分割模型。請注意，*訂單*項目集合中項目間的關係，並不同於 `order`、`shipment`、`orderItem` 及 `invoice` 實體間所擁有的關係。

若想透過 `orderId` 取得運送貨物，您可以利用 `PK=orderId` 和 `SK begins_with “sh#”` 執行查詢操作。

**基礎資料表：**

![\[將包含運送貨物實體的資料表設計新增至訂單項目集合，以依訂單 ID 取得運送貨物。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-7-Step7.png)


**步驟 8：位址存取模式 9 (`getOrderByProductIdForDateRange`)**

在上個步驟中，我們建立了*訂單*項目集合。此存取模式具有新的查詢維度 (`ProductID` 和 `Date`)，會要求您掃描整個資料表並篩選相關記錄，以擷取目標項目。為處理這類存取模式，必須建立[全域次要索引 (GSI)](GSI.md)。匯入 [AnOnlineShop\$110.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_10.json)，利用 GSI 建立新的項目集合，以從數個*訂單*項目集合擷取 `orderItem` 資料。目前資料內有 `GSI1-PK` 和 `GSI1-SK`，分別為 `GSI1` 的分割區索引鍵和排序索引鍵。

DynamoDB 會自動將資料表中包含 GSI 索引鍵屬性的項目填入 GSI，不須額外手動插入 GSI。

若想處理存取模式 9，請利用 `GSI1-PK=productId` 和 `GSI1SK between (date1, date2)` 查詢 `GSI1`。

**基礎資料表：**

![\[包含 GSI 的資料表設計，可從數個訂單項目集合取得訂單資料。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-8-Step8-Base.png)


**GSI1：**

![\[使用 ProductID 和 Date 做為分割區和排序索引鍵的 GSI 設計，依產品 ID 和日期取得訂單。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-9-Step8-GSI.png)


**步驟 9：位址存取模式 10 (`getInvoiceByInvoiceId`) 和 11 (`getPaymentByInvoiceId`)**

匯入 [AnOnlineShop\$111.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_11.json)，處理與 `invoice` 相關的存取模式 10 (`getInvoiceByInvoiceId`) 和 11 (`getPaymentByInvoiceId`)。即使這兩種存取模式並不相同，仍須透過索引鍵條件以實現。`Payments` 的定義為 `invoice` 實體上具地圖資料類型的屬性。

**注意**  
為了儲存不同實體的資訊，`GSI1-PK` 和 `GSI1-SK` 會進行多載，以便從同一個 GSI 取得多個存取模式。如需 GSI 多載的詳細資訊，請參閱 [在 DynamoDB 中多載全域次要索引](bp-gsi-overloading.md)。

若想處理存取模式 10 和 11，請利用 `GSI1-PK=invoiceId` 和 `GSI1-SK=invoiceId` 查詢 `GSI1`。

**GSI1：**

![\[以 invoiceId 做為分割區和排序索引鍵的 GSI 設計，以依發票 ID 取得發票和付款。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-10-Step9.png)


**步驟 10：位址存取模式 12 (`getShipmentDetailsByShipmentId`) 和 13 (`getShipmentByWarehouseId`)**

匯入 [AnOnlineShop\$112.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_12.json)，處理存取模式 12 (`getShipmentDetailsByShipmentId`) 和 13 (`getShipmentByWarehouseId`)。

請注意，`shipmentItem` 實體已新增至基礎資料表上的*訂單*項目集合，以在單一查詢作業中擷取訂單的所有詳細資訊。

**基礎資料表：**

![\[訂單項目集合中包含 shipmentItem 實體的資料表設計，以取得所有訂單詳細資訊。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-11-Step10.png)


`GSI1` 分割區和排序索引鍵已用於建立 `shipment` 和 `shipmentItem` 之間的一對多關係模型。若想處理存取模式 12 (`getShipmentDetailsByShipmentId`)，請利用 `GSI1-PK=shipmentId` 和 `GSI1-SK=shipmentId` 查詢 `GSI1`。

**GSI1：**

![\[使用 shipmentId 做為分割區和排序索引鍵的 GSI1 設計，依運送貨物 ID 取得運送貨物詳細資訊。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-12-Step10-GSI.png)


針對存取模式 13 (`getShipmentByWarehouseId`)，我們必須建立另一個 GSI (`GSI2`)，來為 `warehouse` 和 `shipment` 之間新的一對多關係建立模型。若想處理此存取模式，請利用 `GSI2-PK=warehouseId` 和 `GSI2-SK begins_with “sh#”` 查詢 `GSI2`。

**GSI2：**

![\[使用 warehouseId 和 shipmentId 做為分割區和排序索引鍵的 GSI2 設計，依倉儲取得運送貨物。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-13-Step10-GSI2.png)


**步驟 11：位址存取模式 14 (`getProductInventoryByWarehouseId`)、15 (`getInvoiceByCustomerIdForDateRange`) 和 16 (`getProductsByCustomerIdForDateRange`)**

匯入 [AnOnlineShop\$113.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_13.json)，新增與下一組存取模式相關的資料。若想處理存取模式 14 (`getProductInventoryByWarehouseId`)，請利用 `GSI2-PK=warehouseId` 和 `GSI2-SK begins_with “p#”` 查詢 `GSI2`。

**GSI2：**

![\[使用 warehouseId 和 productId 做為分割區和排序索引鍵的 GSI2 設計，以處理存取模式 14。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-14-Step11-GSI2.png)


若想處理存取模式 15 (`getInvoiceByCustomerIdForDateRange`)，請利用 `GSI2-PK=customerId` 和 `GSI2-SK between (i#date1, i#date2)` 查詢 `GSI2`。

**GSI2：**

![\[使用 customerId 和發票日期範圍做為分割區和排序索引鍵的 GSI2 設計，以處理存取模式 15。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-15-Step11-GSI2.png)


若想處理存取模式 16 (`getProductsByCustomerIdForDateRange`)，請利用 `GSI2-PK=customerId` 和 `GSI2-SK between (p#date1, p#date2)` 查詢 `GSI2`。

**GSI2：**

![\[使用 customerId 和產品日期範圍做為分割區和排序索引鍵的 GSI2 設計，以處理存取模式 16\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-16-Step11-GSI2.png)


**注意**  
在 [NoSQL Workbench](workbench.md) 中，*面向*代表適用於 DynamoDB 的應用程式不同資料存取模式。面向為您提供了一種查看資料表中的資料子集的方法，而無需查看不符合面向限制的記錄。面向是一種視覺資料建模工具，在 DynamoDB 中不作為可用的建構存在，因為其純粹用於輔助存取模式建模。  
匯入 [AnOnlineShop\$1facets.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_facets.json)，以檢視此使用案例的面向。

下表摘要整理了所有存取模式，以及結構描述設計處理這些模式的方式：


| 存取模式 | 基礎資料表/GSI/LSI | 作業 | 分割區索引鍵值 | 排序索引鍵值 | 
| --- | --- | --- | --- | --- | 
| getCustomerByCustomerId | 基礎資料表 | GetItem |  PK=customerId | SK=customerId | 
| getProductByProductId | 基礎資料表 | GetItem |  PK=productId | SK=productId | 
| getWarehouseByWarehouseId | 基礎資料表 | GetItem |  PK=warehouseId | SK=warehouseId | 
| getProductInventoryByProductId | 基礎資料表 | Query |  PK=productId | SK begins\$1with "w\$1" | 
| getOrderDetailsByOrderId | 基礎資料表 | Query |  PK=orderId |  | 
| getProductByOrderId | 基礎資料表 | Query |  PK=orderId | SK begins\$1with "p\$1" | 
| getInvoiceByOrderId |  基礎資料表 | Query |  PK=orderId | SK begins\$1with "i\$1" | 
| getShipmentByOrderId |  基礎資料表 | Query |  PK=orderId | SK begins\$1with "sh\$1" | 
| getOrderByProductIdForDateRange |  GSI1 | Query |  PK=productId | SK between date1 and date2 | 
| getInvoiceByInvoiceId |  GSI1 | Query |  PK=invoiceId | SK=invoiceId | 
| getPaymentByInvoiceId |  GSI1 | Query |  PK=invoiceId | SK=invoiceId | 
| getShipmentDetailsByShipmentId |  GSI1 | Query |  PK=shipmentId | SK=shipmentId | 
| getShipmentByWarehouseId |  GSI2 | Query |  PK=warehouseId | SK begins\$1with "sh\$1" | 
| getProductInventoryByWarehouseId |  GSI2 | Query |  PK=warehouseId | SK begins\$1with "p\$1" | 
| getInvoiceByCustomerIdForDateRange |  GSI2 | Query |  PK=customerId | SK between i\$1date1 and i\$1date2 | 
| getProductsByCustomerIdForDateRange |  GSI2 | Query |  PK=customerId | SK between p\$1date1 and p\$1date2 | 

### 線上商店最終結構描述
<a name="data-modeling-schema-online-store-final-schema"></a>

以下是最終結構描述設計。若要將此結構描述設計下載為 JSON 檔案，請參閱 GitHub 上的 [DynamoDB 設計模式](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples)。

**基礎資料表**

![\[具有 EntityName 和 Name 等屬性的線上商店基礎資料表最終結構描述。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-17-Final-BaseTable.png)


**GSI1**

![\[具有 EntityType 等屬性的線上商店基礎資料表最終 GSI1 結構描述。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-18-Final-GSI1.png)


**GSI2**

![\[具有 EntityType 等屬性的線上商店基礎資料表最終 GSI2 結構描述。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-19-Final-GSI2.png)


## 使用 NoSQL Workbench 與此結構描述設計
<a name="data-modeling-schema-online-shop-nosql"></a>

您可以將此最終結構描述匯入 [NoSQL Workbench](workbench.md)，這是為 DynamoDB 提供資料建模、資料視覺化，和查詢開發功能的視覺化工具，以進一步探索和編輯新專案。請依照下列步驟以開始使用：

1. 下載 NoSQL Workbench。如需詳細資訊，請參閱[下載 DynamoDB 專用 NoSQL Workbench](workbench.settingup.md)。

1. 下載上面列出的 JSON 結構描述檔案，該檔案已經是 NoSQL Workbench 模型格式。

1. 將 JSON 結構描述檔案匯入到 NoSQL Workbench。如需詳細資訊，請參閱[匯入現有的資料模型](workbench.Modeler.ImportExisting.md)。

1. 一旦您匯入到 NOSQL Workbench 後，便可以編輯資料模型。如需詳細資訊，請參閱[編輯現有的資料模型](workbench.Modeler.Edit.md)。

# 在 DynamoDB 中製作關聯式資料模型的最佳實務
<a name="bp-relational-modeling"></a>

本節提供在 Amazon DynamoDB 中建構關聯式資料模型的最佳實務。首先，我們要介紹傳統的資料模型建構概念。接著會說明使用 DynamoDB 相較於傳統關聯式資料庫管理系統的優點，讓您了解它如何消除 JOIN 操作的需求並減少額外負荷。

然後我們將說明如何設計可有效擴展的 DynamoDB 資料表。最後我們會提供一個範例來說明，如何在 DynamoDB 中建模關聯式資料的模型。

**Topics**
+ [傳統關聯式資料庫模型](#SQLtoNoSQL.relational-modeling2)
+ [DynamoDB 如何免除 JOIN 操作的需求](#bp-relational-modeling-joins)
+ [DynamoDB 交易如何消除寫入程序的負荷](#bp-relational-modeling-transactions)
+ [在 DynamoDB 中製作關聯式資料模型的第一步](bp-modeling-nosql.md)
+ [在 DynamoDB 中打造關聯式資料模型的範例](bp-modeling-nosql-B.md)

## 傳統關聯式資料庫模型
<a name="SQLtoNoSQL.relational-modeling2"></a>

傳統關聯式資料庫管理系統 (RDBMS) 會將資料儲存在標準化的關聯式結構中。關聯式資料模型的目標是減少資料的重複 (透過標準化)，以支援參考完整性並減少資料異常。

下列結構描述是一般訂單輸入應用程式的關聯式資料模型範例。此應用程式支援人力資源結構描述，並用它來為虛擬製造商的營運和業務支援系統提供支援。

![\[範例 RDBMS 結構描述。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/RDBMS.png)


由於 DynamoDB 是非關聯式資料庫服務，相較於傳統關聯式資料庫管理系統，它提供了更多優勢。

## DynamoDB 如何免除 JOIN 操作的需求
<a name="bp-relational-modeling-joins"></a>

RDBMS 使用結構式查詢語言 (SQL) 將資料傳回至應用程式。由於資料模型的標準化，此類查詢通常需要使用 `JOIN` 運算子來合併來自一或多個資料表的資料。

例如，若要在可以運送任何項目的所有倉庫中，依庫存中的數量來排序產生購買順序項目清單，您可以對上述結構描述發出以下 SQL 查詢。

```
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 查詢提供存取資料的彈性 API，但他們需要的處理量很大。查詢中的每個聯結都會增加查詢的執行時期複雜度，因為每個資料表的資料必須暫存，然後再組裝以傳回結果集。

可能影響執行查詢時間長短的其他因素包括資料表的大小，以及要聯結的欄是否有索引。上述查詢會在數個資料表中起始複雜查詢，接著排序結果集。

免除使用 `JOINs` 的需求，是 NoSQL 資料建模的核心。這就是為什麼我們建置 DynamoDB 來支援 Amazon.com，以及為什麼 DynamoDB 可以在任何規模提供一致的效能。考慮到 SQL 查詢和 的執行時間複雜性`JOINs`，RDBMS 效能不會大規模保持不變。這會隨著客戶應用程式的增長而導致效能問題。

雖然資料標準化確實可減少儲存到磁碟的資料量，但通常影響效能的最受限資源是 CPU 時間和網路延遲。

DynamoDB 的建置目的是消除 `JOINs` (並鼓勵取消資料標準化) 和最佳化資料庫架構，以便透過對項目的單一請求來完全回應應用程式查詢，將這兩種限制降到最低。這些品質讓 DynamoDB 能夠在任何規模下提供個位數的毫秒速度效能。這是因為 DynamoDB 操作的執行時期複雜性對於常見存取模式來說是固定的，不會隨著資料大小而改變。

## DynamoDB 交易如何消除寫入程序的負荷
<a name="bp-relational-modeling-transactions"></a>

可能降低 RDBMS 速度的另一個因素，是使用交易來寫入標準化結構描述。如範例所示，大部分線上交易處理 (OLTP) 應用程式使用的關聯式資料結構儲存在 RDBMS 中時，必須細分並分散在多個邏輯資料表。

因此，符合 ACID 交易架構是必須的，以避免在應用程式嘗試讀取正處於寫入過程之物件時可能發生的競爭條件和資料完整性問題。此類交易框架與關聯式結構描述結合使用時，會大幅增加寫入程序的負荷。

在 DynamoDB 中實作交易會禁止 RDBMS 上常見的擴展問題發生。DynamoDB 以單一 API 呼叫的形式發出交易，並限制該單一交易中可存取的項目數量，藉此達到這個目的。長時間執行的交易可能因為交易永不關閉，所以長時間或永久鎖定資料，進而導致操作問題。

為了防止 DynamoDB 中發生此類問題，會使用兩個不同的 API 操作來實作交易：`TransactWriteItems` 和 `TransactGetItems`。這些 API 操作沒有 RDBMS 中常見的開始和結束語義。此外，DynamoDB 在交易中有 100 個項目的存取限制，同樣是為了防止交易長時間執行。若要進一步了解 DynamoDB 交易，請參閱[使用交易](transactions.md)。

基於這些原因，當您的企業需要對高流量查詢提供低延遲回應時，在技術與經濟的考量上通常會利用 NoSQL 系統。Amazon DynamoDB 可避免這些問題，協助解決限制關聯式系統可擴展性的問題。

基於以下原因，RDBMS 的效能通常無法適當地擴展：
+ 因此會使用昂貴的聯結來重新組合必要的查詢結果檢視。
+ 資料庫將資料標準化並存放在多個資料表中，這些資料表需要多個查詢來寫入至磁碟中。
+ 這通常會引發 ACID 合規交易系統的效能成本，

DynamoDB 順利擴展的原因為下：
+ 結構描述靈活性可讓 DynamoDB 在單一項目中存放複雜階層資料。
+ 複合索引鍵設計可讓您將相關的項目存放在相同資料表的鄰近位置。
+ 交易以單一操作形式執行。可存取的項目數限制為 100 個，以避免操作長時間執行。

對資料存放的查詢變得簡單多了，通常是使用以下格式：

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

與先前範例中的 RDBMS 相比，DynamoDB 傳回要求資料的程序簡單許多。

# 在 DynamoDB 中製作關聯式資料模型的第一步
<a name="bp-modeling-nosql"></a>

**注意**  
NoSQL 設計思維與 RDBMS 設計不同。針對 RDBMS，您可以建立標準化的資料模型，而不需考量存取模式。您可以在稍後有新問題與查詢要求時擴展此模型。相反地，在 Amazon DynamoDB 中，您不應開始設計結構描述，除非您知道其需要回答的問題。您絕對必須事先了解企業問題和應用程式使用案例。

若要開始設計能有效擴展的 DynamoDB 資料表，您必須先執行數個步驟，以識別操作所需的存取模式與其需要支援的企業支援系統 (OSS/BSS)：
+ 針對新應用程式，檢視活動與目標相關的使用者案例。記錄您識別的各種使用案例，並分析案例需要的存取模式。
+ 針對現有應用程式，分析查詢記錄以找出人員目前使用系統的方式與索引鍵存取模式為何。

完成此程序後，您將收到一個清單，其看起來可能與以下類似。


**訂單項目應用程式的存取模式**  

| 模式 \$1 | 存取模式描述 | 
| --- | --- | 
| 1 | 按員工 ID 查詢員工詳細資訊 | 
| 2 | 按員工姓名查詢員工詳細資訊 | 
| 3 | 尋找員工的電話號碼 (s) | 
| 4 | 尋找客戶的電話號碼 (s) | 
| 5 | 在日期範圍內為客戶取得訂單 | 
| 6 | 顯示日期範圍內的所有開啟的訂單 | 
| 7 | 查看所有最近僱用的員工 | 
| 8 | 尋找 Warehouse 中的所有員工 | 
| 9 | 取得產品訂單上的所有項目 | 
| 10 | 在所有倉儲取得產品的庫存 | 
| 11 | 依帳戶代表取得客戶 | 
| 12 | 依帳戶代表取得訂單 | 
| 13 | 取得具有任務標題的員工 | 
| 14 | 依產品和倉儲取得庫存 | 
| 15 | 取得產品庫存總計 | 

在實際應用中，您的清單可能會更長。但此集合代表您可能會在生產環境中找到的查詢模式複雜度範圍。

DynamoDB 結構描述設計的現代方法使用彙總導向原則，根據存取模式而非剛性實體邊界來分組資料。此方法考慮多種設計模式：
+ *單一資料表設計* - 使用複合排序索引鍵、超載的全域次要索引和相鄰清單模式，在一個資料表中存放多個實體類型
+ *多資料表設計* - 針對具有獨立操作特性和低存取相關性的實體使用單獨的資料表，以及跨實體查詢的策略 GSIs 
+ *彙總設計* - 一律同時存取時嵌入相關資料 （訂單 \$1 OrderItems) 或使用項目集合來識別關係 （產品 \$1 庫存）

這些方法的選擇取決於您的特定存取模式、資料特性和操作需求。您可以使用這些元素來建構資料，如此應用程式就可以使用對資料表或索引的單一查詢來擷取特定存取模式所需的項目。

**注意**  
單一資料表和多資料表設計之間的選擇取決於您的特定需求。當實體具有高存取相互關聯性和類似的操作特性時，單一資料表設計可正常運作。當實體有獨立的操作需求、不同的存取模式，或是您需要明確的操作界限時，最好採用多資料表設計。本指南中的範例示範了具有策略彙總和非標準化的多資料表方法。

若要使用適用於 DynamoDB 的 NoSQL Workbench 來協助視覺化您的分割區索引鍵設計，請參閱 [使用 NoSQL Workbench 建立資料模型](workbench.Modeler.md)。

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

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

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

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

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

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

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


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

| employee\$1id (PK) | name | phone\$1numbers | 倉儲 ID | job\$1title | hire\$1date | entity\$1type | 
| --- | --- | --- | --- | --- | --- | --- | 
| emp\$1001 | John Smith | 【"\$11-555-0101"】 | wh\$1sea | 管理員 | 2024-03-15 | 員工 | 
| emp\$1002 | Jane Doe | 【"\$11-555-0102"、"\$11-555-0103"】 | wh\$1sea | 關聯 | 2025-01-10 | 員工 | 
| emp\$1003 | Bob Wilson | 【"\$11-555-0104"】 | wh\$1pdx | 關聯 | 2025-06-20 | 員工 | 
| emp\$1004 | Alice 棕色 | 【"\$11-555-0105"】 | wh\$1pdx | 主管 | 2023-11-05 | 員工 | 
| emp\$1005 | Charlie Davis | 【"\$11-555-0106"】 | wh\$1sea | 關聯 | 2025-12-01 | 員工 | 


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

| 名稱 (GSI-PK) | employee\$1id (GSI-SK) | phone\$1numbers | 倉儲 ID | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | --- | 
| Alice 棕色 | emp\$1004 | 【"\$11-555-0105"】 | wh\$1pdx | 主管 | 2023-11-05 | 
| Bob Wilson | emp\$1003 | 【"\$11-555-0104"】 | wh\$1pdx | 關聯 | 2025-06-20 | 
| Charlie Davis | emp\$1005 | 【"\$11-555-0106"】 | wh\$1sea | 關聯 | 2025-12-01 | 
| Jane Doe | emp\$1002 | 【"\$11-555-0102"、"\$11-555-0103"】 | wh\$1sea | 關聯 | 2025-01-10 | 
| John Smith | emp\$1001 | 【"\$11-555-0101"】 | wh\$1sea | 管理員 | 2024-03-15 | 


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

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


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

| job\$1title (GSI-PK) | employee\$1id (GSI-SK) | name | 倉儲 ID | hire\$1date | 
| --- | --- | --- | --- | --- | 
| 關聯 | emp\$1002 | Jane Doe | wh\$1sea | 2025-01-10 | 
| 關聯 | emp\$1003 | Bob Wilson | wh\$1pdx | 2025-06-20 | 
| 關聯 | emp\$1005 | Charlie Davis | wh\$1sea | 2025-12-01 | 
| 管理員 | emp\$1001 | John Smith | wh\$1sea | 2024-03-15 | 
| 主管 | emp\$1004 | Alice 棕色 | wh\$1pdx | 2023-11-05 | 


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

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

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

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

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


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

| 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 | 全球交易員 | 【"\$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 - 支援帳戶代表查詢**  

| 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 | 全球交易員 | sales@globaltraders.com | 
| rep\$1002 | cust\$1004 | BuildRight LLC | orders@buildright.com | 
| rep\$1003 | cust\$1005 | FastShip Co | support@fastship.com | 

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

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

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

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

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


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

| PK | SK | customer\$1id | order\$1date | status | account\$1rep\$1id | 數量 | 價格 | 碎片 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| 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 - 支援客戶訂單查詢**  

| customer\$1id (GSI-PK) | order\$1date (GSI-SK) | order\$1id | status | total\$1amount | order\$1items | 碎片 | 
| --- | --- | --- | --- | --- | --- | --- | 
| cust\$1001 | 2025-11-15 | ord\$1001 | CLOSED | 225.00 | 【\$1product\$1id： "prod\$1100"，數量：5\$1】 |  | 
| cust\$1001 | 2025-12-20 | ord\$1002 | OPEN | 150.00 | 【\$1product\$1id： "prod\$1101"，數量：10\$1】 | 0 | 
| cust\$1002 | 2026-01-05 | ord\$1003 | OPEN | 175.00 | 【\$1product\$1id： "prod\$1100"，數量：3\$1】 | 2 | 
| cust\$1003 | 2025-10-10 | ord\$1004 | CLOSED | 250.00 | 【\$1product\$1id： "prod\$1101"，數量：5\$1】 |  | 
| cust\$1004 | 2026-01-03 | ord\$1005 | OPEN | 200.00 | 【\$1product\$1id： "prod\$1100"，數量：20\$1】 | 1 | 


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

| 狀態 (GSI-PK-1) | 碎片 (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"，數量：10\$1】 | 150.00 | 
| OPEN | 1 | 2026-01-03 | ord\$1005 | cust\$1004 | rep\$1002 | 【\$1product\$1id： "prod\$1100"，數量：20\$1】 | 200.00 | 
| OPEN | 2 | 2026-01-05 | ord\$1003 | cust\$1002 | rep\$1001 | 【\$1product\$1id： "prod\$1100"，數量：3\$1】 | 175.00 | 


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

| 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 - 支援產品訂單查詢**  

| product\$1id (GSI-PK) | order\$1id (GSI-SK) | customer\$1id | order\$1date | 數量 | 
| --- | --- | --- | --- | --- | 
| 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 | 

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

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

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


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

| product\$1id (PK) | warehouse\$1id (SK) | product\$1name | category | unit\$1price | inventory\$1quantity | total\$1inventory | 
| --- | --- | --- | --- | --- | --- | --- | 
| prod\$1100 | prod\$1100 | 小工具 A | 硬體 | 25.00 |  | 500 | 
| prod\$1100 | wh\$1sea |  |  |  | 200 |  | 
| prod\$1100 | wh\$1pdx |  |  |  | 150 |  | 
| prod\$1100 | wh\$1atl |  |  |  | 150 |  | 
| prod\$1101 | prod\$1101 | 小工具 B | 電子 | 50.00 |  | 300 | 
| prod\$1101 | wh\$1sea |  |  |  | 100 |  | 
| prod\$1101 | wh\$1pdx |  |  |  | 200 |  | 

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

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

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


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