

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

# 本機次要索引
<a name="LSI"></a>

某些應用程式只需要使用基礎資料表的主索引鍵查詢資料。但是，在某些情況下，替代排序索引鍵會有所幫助。為了給應用程式提供排序索引鍵選擇，您可以在 Amazon DynamoDB 資料表上建立一或多個本機次要索引，並針對這些索引發出 `Query` 或 `Scan` 請求。

**Topics**
+ [案例：使用本機次要索引](#LSI.Scenario)
+ [屬性投影](#LSI.Projections)
+ [建立本機次要索引](#LSI.Creating)
+ [從本機次要索引讀取資料](#LSI.Reading)
+ [項目寫入和本機次要索引](#LSI.Writes)
+ [本機次要索引的佈建輸送量考量](#LSI.ThroughputConsiderations)
+ [本機次要索引的儲存考量](#LSI.StorageConsiderations)
+ [本機次要索引中的項目集合](#LSI.ItemCollections)
+ [使用本機次要索引：Java](LSIJavaDocumentAPI.md)
+ [使用本機次要索引：.NET](LSILowLevelDotNet.md)
+ [在 DynamoDB AWS CLI 中使用本機次要索引](LCICli.md)

## 案例：使用本機次要索引
<a name="LSI.Scenario"></a>

例如，請考慮 `Thread` 資料表。此資料表適用於應用程式，例如 [AWS 開發論壇](https://forums.aws.amazon.com/)。下圖顯示這些項目在資料表中的組織方式。(並未顯示所有屬性。)

![\[主題資料表包含論壇名稱、話題、上次貼文時間和回覆次數的清單。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/LSI_01.png)


DynamoDB 會連續儲存具有相同分割區索引鍵值的所有項目。在本例中，給定一個特定的 `ForumName`，`Query` 操作就可以立即找到該論壇的所有主題。在具有相同分割區索引鍵值的項目群組中，項目會依排序索引鍵值排序。如果查詢中還提供了排序索引鍵 (`Subject`)，DynamoDB 可以縮小傳回的結果範圍，例如傳回「S3」論壇中以字母「a」開頭的 `Subject` 的所有主題。

某些請求可能需要更複雜的資料存取模式。例如：
+ 哪些論壇主題獲得最多的觀看次數和回覆？
+ 特定論壇中哪個主題的訊息數量最多？
+ 在特定時段內，有多少個主題發布在特定論壇？

若要回答這些問題，`Query` 動作並不足夠。您還必須 `Scan` 整個資料表。對於包含數百萬個項目的資料表，這會消耗大量佈建的讀取輸送量，而且需要很長的時間才能完成。

不過，您可以在非索引鍵屬性上指定一或多個本機次要索引，例如 `Replies` 或 `LastPostDateTime`。

*本機次要索引*會針對指定分割區索引鍵值維護替代排序索引鍵。本機次要索引也包含基礎資料表中部分或全部屬性的複本。您可以指定在建立資料表時要投影到本機次要索引中的屬性。本機次要索引中的資料由與基礎資料表相同的分割區索引鍵組織，但具有不同的排序索引鍵。這可讓您在此不同維度之間有效地存取資料項目。為獲得更大的查詢或掃描靈活性，您最多可以為每個資料表建立五個本機次要索引。

假設應用程式需要尋找在最近三個月內張貼於特定論壇中的所有主題。如果沒有本機次要索引，應用程式必須 `Scan` 整個 `Thread` 資料表，並捨棄任何不在指定時間範圍內的貼文。使用本機次要索引，`Query` 操作可以使用 `LastPostDateTime` 作為排序索引鍵，並快速找到資料。

下圖顯示名為 `LastPostIndex` 的本機次要索引。請注意，分割區索引鍵與 `Thread` 資料表的分割區索引鍵相同，但排序索引鍵是 `LastPostDateTime`。

![\[包含論壇名稱、主題和上次貼文時間的 LastPostIndex 資料表。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/LSI_02.png)


每個本機次要索引均須符合下列條件：
+ 分割區索引鍵與其基礎資料表的分割區索引鍵相同。
+ 排序索引鍵只包含一個純量屬性。
+ 基礎資料表的排序索引鍵會投影到索引中，在索引中充當非索引鍵屬性。

在本例中，分割區索引鍵是 `ForumName`，而本機次要索引的排序索引鍵是 `LastPostDateTime`。此外，基礎資料表中的排序索引鍵值 (在本例中為 `Subject`) 會投影到索引中，但不是索引鍵的一部分。如果應用程式需要以 `ForumName` 和 `LastPostDateTime` 為基礎的清單，則可針對 `LastPostIndex` 發出 `Query` 請求。查詢結果會依 `LastPostDateTime` 排序，並且可以依升序或降序排列傳回。查詢也可以套用索引鍵條件，例如僅傳回在特定的時間範圍內具有 `LastPostDateTime` 的項目。

每個本機次要索引會自動包含來自其基礎資料表的分割區索引鍵和排序索引鍵；您可以選擇將非索引鍵屬性投影到索引中。當您查詢索引時，DynamoDB 可有效率地擷取這些投影屬性。在查詢本機次要索引時，查詢還可以檢索*未*投影到索引中的屬性。DynamoDB 會自動從基礎資料表擷取這些屬性，但延遲較大，且佈建的輸送量成本也較高。

對於任何本機次要索引，每個不同的分割區索引鍵值最多可存放 10 GB 的資料。此圖包含基礎資料表中的所有項目，以及索引中具有相同分割區索引鍵值的所有項目。如需詳細資訊，請參閱 [本機次要索引中的項目集合](#LSI.ItemCollections)。

## 屬性投影
<a name="LSI.Projections"></a>

應用程式可以借助 `LastPostIndex` 將 `ForumName` 和 `LastPostDateTime` 用作查詢條件。不過，若要擷取任何其他屬性，DynamoDB 必須對 `Thread` 資料表執行額外的讀取操作。這些額外讀取稱為*擷取*，可以增加查詢所需的佈建輸送量總量。

假設您想要填入一個網頁，其中包含「S3」中所有主題的清單，以及每個主題的回覆次數 (依上次回覆日期/時間，從最近回覆開始排序)。若要填入此清單，您需要下列屬性：
+ `Subject`
+ `Replies`
+ `LastPostDateTime`

查詢這些資料並避免擷取操作的最有效方法是將 `Replies` 屬性從資料表投影至本機次要索引，如此圖所示。

![\[包含論壇名稱、上次貼文時間、主題和回覆的清單的 LastPostIndex 資料表。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/LSI_03.png)




*投影*是指從資料表複製到次要索引的屬性集合。資料表的分割區索引鍵和排序索引鍵一律會投影到索引中；您可以投影其他屬性來支援應用程式的查詢需求。查詢索引時，Amazon DynamoDB 可以存取投影中的任何屬性，就好像這些屬性在它們自己的資料表中一樣。

在建立次要索引時，您需要指定要投影到索引中的屬性。DynamoDB 為此提供三種不同的選項：
+ *KEYS\$1ONLY*：索引中的每個項目都只包含資料表分割索引鍵和排序索引鍵值，以及索引鍵值。`KEYS_ONLY` 選項會產生最小的可行次要索引。
+ *INCLUDE*：除了 `KEYS_ONLY` 中描述的屬性外，次要索引還包含您指定的其他非索引鍵屬性。
+ *ALL*：次要索引包含來源資料表中的所有屬性。因為索引中的所有資料表資料都會重複，所以 `ALL` 投影會產生最大的可行次要索引。

在上圖中，非索引鍵屬性 `Replies` 投影到 `LastPostIndex`。應用程式可以查詢 `LastPostIndex`，而不是完整的 `Thread` 資料表，以便使用 `Subject`、`Replies` 和 `LastPostDateTime` 來填充網頁。如果請求任何其他非索引鍵屬性，DynamoDB 需要從 `Thread` 資料表擷取這些屬性。

從應用程式的角度來看，從基礎資料表擷取其他屬性是自動且透明的程序，因此不需要重寫任何應用程式邏輯。不過，此類擷取會大幅降低使用本機次要索引的效能優勢。

在選擇要投影到本機次要索引的屬性時，您必須權衡佈建輸送量成本和儲存成本：
+ 若您只需要存取少量的屬性，並且希望盡可能的降低延遲，建議您考慮只將那些屬性投影到本機次要索引。索引愈小，存放成本就愈低，您的寫入成本也愈低。如果您偶爾需要擷取屬性，佈建輸送量的成本可能會超過儲存這些屬性的長期成本。
+ 如果應用程式會頻繁存取某些非索引鍵屬性，您應該考慮將這些屬性投影到本機次要索引。本機次要索引的額外儲存成本會抵銷頻繁執行資料表掃描的成本。
+ 如果需要頻繁存取大多數的非索引鍵屬性，您可以將這些屬性 (或整個基礎資料表) 投影到本機次要索引。這可為您提供最大的靈活性和最低的佈建輸送量耗用量，因為不需要擷取。但是，如果投影所有屬性，您的儲存成本將會增加，甚至翻倍。
+ 如果您的應用程式不需頻繁查詢資料表，但必須對資料表中的資料執行許多寫入或更新，請考慮投影 *KEYS\$1ONLY*。本機次要索引的大小將會最小，但仍能在需要的時候進行查詢活動。

## 建立本機次要索引
<a name="LSI.Creating"></a>

若要在資料表上建立具有一或多個本機次要索引，請使用 `CreateTable` 操作的 `LocalSecondaryIndexes` 參數。建立資料表時，會建立資料表上的本機次要索引。當您使用刪除資料表時，也會刪除該資料表上的任何本機次要索引。

您必須指定一個非索引鍵屬性作為本機次要索引的排序索引鍵。您選擇的屬性必須是純量 `String`、`Number` 或 `Binary`。不允許其他純量類型、文件類型和集合類型。如需資料類型的完整清單，請參閱 [資料類型](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)。

**重要**  
對於具有本機次要索引的資料表，每個分割區索引鍵值都有 10 GB 的大小限制。具有本機次要索引的資料表可以存放任何數量的項目，只要任何一個分割區索引鍵值的總大小不超過 10 GB 即可。如需詳細資訊，請參閱 [項目集合大小限制](#LSI.ItemCollections.SizeLimit)。

您可以將任何資料類型的屬性投影到本機次要索引。這包括純量、文件和集合。如需資料類型的完整清單，請參閱 [資料類型](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)。

## 從本機次要索引讀取資料
<a name="LSI.Reading"></a>

您可以使用 `Query` 和 `Scan` 操作從本機次要索引擷取項目。`GetItem` 和 `BatchGetItem` 操作不能用於本機次要索引。

### 查詢本機次要索引
<a name="LSI.Querying"></a>

在 DynamoDB 資料表中，每個項目的組合分割區索引鍵值和排序索引鍵值必須是唯一的值。不過，在本機次要索引中，排序索引鍵值對於指定的分割區索引鍵值不一定是唯一的值。如果本機次要索引中有多個項目具有相同的排序索引鍵值，`Query` 操作會傳回具有相同分割區索引鍵值的所有項目。在回應中，符合的項目不會以任何特定的順序傳回。

您可以使用最終一致或高度一致性讀取來查詢本機次要索引。若要指定您想要的一致性類型，請使用 `Query` 操作的 `ConsistentRead` 參數。來自本機次要索引的高度一致性讀取一律會傳回最新的更新值。如果查詢需要從基礎資料表中取得其他屬性，這些屬性將與索引保持一致。

**Example**  
請考慮從 `Query` (請求來自特定論壇主題的資料) 傳回的下列資料。  

```
{
    "TableName": "Thread",
    "IndexName": "LastPostIndex",
    "ConsistentRead": false,
    "ProjectionExpression": "Subject, LastPostDateTime, Replies, Tags",
    "KeyConditionExpression": 
        "ForumName = :v_forum and LastPostDateTime between :v_start and :v_end",
    "ExpressionAttributeValues": {
        ":v_start": {"S": "2015-08-31T00:00:00.000Z"},
        ":v_end": {"S": "2015-11-31T00:00:00.000Z"},
        ":v_forum": {"S": "EC2"}
    }
}
```
在此查詢中：  
+ DynamoDB 存取 `LastPostIndex`，使用 `ForumName` 分割區索引鍵尋找「EC2」的索引項目。所有具有此索引鍵的索引項目都會相鄰存放，以利快速擷取。
+ 在此論壇中，DynamoDB 使用索引來尋找符合指定 `LastPostDateTime` 條件的索引鍵。
+ 由於 `Replies` 屬性投影到索引中時，DynamoDB 可以檢索此屬性，而不會耗用任何額外的佈建輸送量。
+ 此 `Tags` 屬性不會投影到索引中，因此 DynamoDB 必須存取 `Thread` 資料表並擷取此屬性。
+ 結果會傳回，並依 `LastPostDateTime` 排序。索引項目依分割區索引鍵值排序，再依排序索引鍵值排序，然後由 `Query` 依儲存的順序將其傳回。(您可以使用 `ScanIndexForward` 參數，以遞減順序傳回結果。)
由於 `Tags` 屬性未投影到本機次要索引中，DynamoDB 必須消耗額外的讀取容量單位，才能從基礎資料表擷取此屬性。如果需要經常執行此查詢，您應將 `Tags` 投影至 `LastPostIndex`，避免從基礎資料表擷取。不過，如果您僅偶爾需要存取 `Tags`，則將 `Tags` 投影至索引中的額外儲存成本便可能不值得。

### 掃描本機次要索引
<a name="LSI.Scanning"></a>

您可以使用 `Scan` 從本機次要索引檢索所有資料。您必須在請求中提供基礎資料表名稱及索引名稱。使用 `Scan`，DynamoDB 會讀取索引中的所有資料，並將其傳回應用程式。您也可以請求只傳回一部分的資料，並捨棄其他剩餘的資料。若要執行此作業，請使用 `Scan` API 的 `FilterExpression` 參數。如需詳細資訊，請參閱 [掃描的篩選條件表達式](Scan.md#Scan.FilterExpression)。

## 項目寫入和本機次要索引
<a name="LSI.Writes"></a>

DynamoDB 會自動讓所有本機次要索引與各自的基礎資料表同步。應用程式永遠不會直接寫入索引。然而，了解 DynamoDB 維持這些索引之方式的含義很重要。

在建立本機次要索引時，您可以指定一個屬性作為索引的排序索引鍵。您也可以為該屬性指定資料類型。這表示每次您將項目寫入基礎資料表時，如果項目定義了索引鍵屬性，則其類型必須與索引鍵結構描述的資料類型相符。在 `LastPostIndex` 案例中，索引的 `LastPostDateTime` 排序索引鍵是定義為 `String` 資料類型。如果您嘗試在 `Thread` 資料表中新增項目，並為 `LastPostDateTime` (如 `Number`) 指定不同的資料類型，DynamoDB 會因資料類型不符而傳回 `ValidationException`。

基礎資料表中的項目與本機次要索引中的項目之間沒有一對一的關係要求。事實上，這種行為對許多應用程式來說可能是有利的。

相較於索引較少的資料表，本機次要索引較多的資料表會有較高的寫入活動成本。如需詳細資訊，請參閱 [本機次要索引的佈建輸送量考量](#LSI.ThroughputConsiderations)。

**重要**  
對於具有本機次要索引的資料表，每個分割區索引鍵值都有 10 GB 的大小限制。具有本機次要索引的資料表可以存放任何數量的項目，只要任何一個分割區索引鍵值的總大小不超過 10 GB 即可。如需詳細資訊，請參閱 [項目集合大小限制](#LSI.ItemCollections.SizeLimit)。

## 本機次要索引的佈建輸送量考量
<a name="LSI.ThroughputConsiderations"></a>

在 DynamoDB 中建立資料表時，您可以為資料表的預期工作負載佈建讀取和寫入容量單位。該工作負載包括在資料表本機次要索引上的讀取和寫入活動。

若要檢視佈建的輸送容量的目前費率，請參閱 [Amazon DynamoDB 定價](https://aws.amazon.com/dynamodb/pricing)。

### 讀取容量單位
<a name="LSI.ThroughputConsiderations.Reads"></a>

在查詢本機次要索引時，消耗的讀取容量單位數目取決於資料的存取方式。

與資料表查詢一樣，索引查詢可以使用最終一致性或高度一致性讀取，取決於 `ConsistentRead` 的值。一個高度一致性讀取會消耗一個讀取容量單位；最終一致讀取則只會消耗一半的讀取容量單位。因此，透過選擇最終一致讀取，您可以減少讀取容量單位的費用。

對於僅請求索引鍵和投影屬性的索引查詢，DynamoDB 會使用為資料表查詢計算佈建讀取活動相同的方式，計算佈建讀取活動。唯一的差別在於計算方式是以索引項目的大小為基礎，而非基礎資料表中項目的大小。讀取容量單位數為所有傳回項目中，所有投影屬性大小的總和；結果會四捨五入至下一個 4 KB 界限。如需 DynamoDB 如何計算佈建輸送用量的詳細資訊，請參閱 [DynamoDB 佈建容量模式](provisioned-capacity-mode.md)。

對於讀取未投影到本機次要索引的屬性的索引查詢，除了從索引讀取投影的屬性之外，DynamoDB 還需要從基礎資料表擷取這些屬性。當您在 `Query` 操作的 `Select` 或 `ProjectionExpression` 參數中包含任何非投影屬性，即會發生這些擷取。擷取會在查詢回應中造成額外的延遲，也會產生較高的佈建輸送量成本：除了從先前描述的本機次要索引讀取之外，您還需要針對每個擷取的基礎資料表項目支付讀取容量單位費用。此費用是用於讀取資料表中的每個項目，而不僅僅是請求的屬性。

`Query` 操作傳回的結果大小上限是 1 MB。這包含所有傳回項目之所有屬性名稱與值的大小。不過，如果針對本機次要索引進行查詢導致 DynamoDB 從基礎資料表擷取項目屬性，則結果中資料的大小上限可能會降低。在這種情況下，結果大小是以下各項的總和：
+ 索引中相符項目的大小，且項目大小會四捨五入至下一個 4 KB。
+ 基礎資料表中每個相符項目的大小，且每個項目的大小會四捨五入至下一個 4 KB。

若使用此公式，查詢操作傳回的結果的大小上限仍為 1 MB。

例如，假設有一個資料表，其中每個項目的大小均為 300 個位元組。該資料表上有本機次要索引，但每個項目只有 200 個位元組投影到索引中。現在假設您 `Query` 此索引，查詢將需為每個項目擷取資料表，且查詢會傳回 4 個項目。DynamoDB 會計算以下總數：
+ 索引中相符項目的大小：200 個位元組 × 4 個項目 = 800 個位元組；接著會四捨五入至 4 KB。
+ 基礎資料表中每個相符項目的大小：(300 個位元組，四捨五入至 4 KB) × 4 個項目 = 16 KB。

因此，結果中的資料總大小為 20 KB。

### 寫入容量單位
<a name="LSI.ThroughputConsiderations.Writes"></a>

當新增、更新或刪除資料表中的項目時，更新本機次要索引會耗用資料表的佈建寫入容量單位。寫入的總佈建輸送量成本為寫入資料表使用的寫入容量單位，加上更新本機次要索引使用的寫入容量單位。

將項目寫入本機次要索引的成本取決於幾個因素：
+ 若您將項目寫入定義索引屬性的資料表，或是您將現有的項目更新為先前未定義的索引屬性，則將該項目寫入索引需要進行一次寫入操作。
+ 若資料表的更新會變更索引鍵屬性的值 (從 A 到 B)，則需要兩次寫入：一次是從索引刪除先前的項目，第二次則是將新的項目寫入索引。  
+ 若項目存在於索引中，但寫入資料表致使索引屬性遭到刪除，則需要進行一次寫入，從索引刪除舊項目的投影。
+ 若項目在更新之前或之後並不存在於索引中，則該索引將不會有任何額外的寫入成本。

所有這些因素都假設索引中每個項目的大小都小於或等於 1 KB 項目大小 (用於計算寫入容量單位)。較大的索引項目需要額外的寫入容量單位。您可以考慮查詢需要傳回的屬性，並只將那些屬性投影到索引中，從而將寫入成本降至最小。

## 本機次要索引的儲存考量
<a name="LSI.StorageConsiderations"></a>

當應用程式將項目寫入資料表時，DynamoDB 會自動將正確的部分屬性複製到應顯示這些屬性的任何本機次要索引中。 AWS 您的帳戶需要支付基本資料表中項目的儲存費用，以及該資料表上任何本機次要索引中屬性的儲存費用。

索引項目使用的空間數為下列項目的總和：
+ 基礎資料表主索引鍵 (分割區索引鍵和排序索引鍵) 的大小 (位元組)
+ 索引鍵屬性的大小 (位元組)
+ 投影屬性 (若有的話) 的大小 (位元組)
+ 每個索引項目 100 位元組的額外負荷

若要估算本機次要索引的儲存需求，您可以估算索引中項目的平均大小，再乘以索引中的項目數量。

如果資料表包含特定屬性未經定義的項目，但該屬性卻已定義為索引的排序索引鍵時，DynamoDB 不會將該項目的任何資料寫入索引。

## 本機次要索引中的項目集合
<a name="LSI.ItemCollections"></a>

**注意**  
此節僅適用於具有本機次要索引的資料表。

在 DynamoDB 中，*項目集合*是資料表及其所有本機次要索引中具有相同分割區索引鍵值的任何項目群組。在本節所使用的範例中，`Thread` 資料表的分割區索引鍵為 `ForumName`，`LastPostIndex` 的分割區索引鍵也為 `ForumName`。所有具有相同 `ForumName` 的資料表和索引項目均屬於相同的項目集合。例如，在 `Thread` 資料表與 `LastPostIndex` 本機次要索引中，論壇 `EC2` 有一個項目集合，論壇 `RDS` 則有一個不同的項目集合。

下圖顯示論壇 `S3` 的項目集合。

![\[DynamoDB 項目集合，其中的資料表及本機次要索引項目均具有相同分割區索引鍵值 S3。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/LSI_04.png)


在此圖表中，項目集合由 `Thread` 和 `LastPostIndex` 中的所有項目組成，其中 `ForumName` 分割區索引鍵值為「S3」。如果資料表上有其他本機次要索引，則這些索引中 `ForumName` 等於「S3」的任何項目也將成為項目集合的一部分。

您可以在 DynamoDB 中使用下列任何操作來傳回項目集合的相關資訊：
+ `BatchWriteItem`
+ `DeleteItem`
+ `PutItem`
+ `UpdateItem`
+ `TransactWriteItems`

這些操作中的每一個都支援 `ReturnItemCollectionMetrics` 參數。在將此參數設定為 `SIZE`，您可以檢視索引中每個項目集合大小的相關資訊。

**Example**  
以下是 `Thread` 資料表上 `UpdateItem` 操作的輸出範例，其中 `ReturnItemCollectionMetrics` 設定為 `SIZE`。更新的項目的 `ForumName` 值為「EC2」，因此輸出包含有關該項目集合的資訊。  

```
{
    ItemCollectionMetrics: {
        ItemCollectionKey: {
            ForumName: "EC2"
        },
        SizeEstimateRangeGB: [0.0, 1.0]
    }
}
```
`SizeEstimateRangeGB` 物件會顯示此項目集合的大小介於 0 到 1 GB 之間。DynamoDB 會定期更新此大小估計值，因此下次修改項目時，數字可能會有所不同。

### 項目集合大小限制
<a name="LSI.ItemCollections.SizeLimit"></a>

包含一或多個本機次要索引之資料表，其任何項目集合的大小上限為 10GB。這不適用於沒有本機次要索引之資料表的項目集合，也不適用於全域次要索引中的項目集合。僅具有一或多個本機次要索引的資料表才會受到影響。

如果項目集合超過 10 GB 的限制，DynamoDB 可能會傳回 `ItemCollectionSizeLimitExceededException`，而且您可能無法將更多項目新增至項目集合，或增加項目集合中項目的大小。(仍允許會縮小項目集合大小的讀取和寫入操作。) 您仍可將項目新增至其他項目集合。

若要減少項目集合的大小，您可以執行下列操作之一：
+ 刪除有問題的分割區索引鍵值的任何不必要項目。在從基礎資料表中刪除這些項目時，DynamoDB 也會移除具有相同分割區索引鍵值的任何索引項目。
+ 透過移除屬性或減少屬性大小來更新項目。如果這些屬性投影到任何本機次要索引中，DynamoDB 也會減少對應索引項目的大小。
+ 使用相同的分割區索引鍵和排序索引鍵來建立新資料表，然後將項目從舊資料表移至新資料表。如果資料表具有不常存取的歷史資料，這可能是一個很好的方法。您也可以考慮將此歷史資料封存至 Amazon Simple Storage Service (Amazon S3)。

當項目集合的總大小降到 10 GB 以下時，您可以再次新增具有相同分割區索引鍵值的項目。

作為最佳實務，我們建議您檢測應用程式以監控項目集合的大小。方法之一是在使用 `BatchWriteItem`、`DeleteItem`、`PutItem` 或 `UpdateItem` 時將 `ReturnItemCollectionMetrics` 參數設定為 `SIZE`。您的應用程式應該檢查輸出中的 `ReturnItemCollectionMetrics` 物件，並在項目集合超過使用者定義的限制 (例如 8 GB) 時記錄錯誤訊息。設定小於 10 GB 的限制會提供一個預警系統，讓您及時掌握項目集合正接近限制，以便採取相關措施。

### 項目集合和分割區
<a name="LSI.ItemCollections.OnePartition"></a>

在包含一或多個本機次要索引的資料表中，每個項目集合會儲存在一個分割區中。此項目集合的總大小僅限於該分割區的容量：10GB。應用程式中，若資料模型包含大小無上限的項目集合，或您可能合理預期某些項目集合日後會超過 10GB，您應考慮改用全域次要索引。

您應將應用程式設計為讓資料表資料均勻分佈在不同的分割區索引鍵值之間。對於具有本機次要索引的資料表，您的應用程式不應在單一分割區上的單一項目集合內建立讀取和寫入活動的「熱點」。

# 使用本機次要索引：Java
<a name="LSIJavaDocumentAPI"></a>

您可以使用 適用於 Java 的 AWS SDK 文件 API 建立具有一或多個本機次要索引的 Amazon DynamoDB 資料表、描述資料表上的索引，並使用索引執行查詢。

以下是使用 適用於 Java 的 AWS SDK 文件 API 進行資料表操作的常見步驟。

1. 建立 `DynamoDB` 類別的執行個體。

1. 透過建立對應的請求物件，為操作提供必要及選用的參數。

1. 呼叫您在前一步驟中建立之用戶端所提供的適當方法。

**Topics**
+ [使用本機次要索引建立資料表](#LSIJavaDocumentAPI.CreateTableWithIndex)
+ [使用本機次要索引描述資料表](#LSIJavaDocumentAPI.DescribeTableWithIndex)
+ [查詢本機次要索引](#LSIJavaDocumentAPI.QueryAnIndex)
+ [範例：使用 Java Document API 的本機次要索引](LSIJavaDocumentAPI.Example.md)

## 使用本機次要索引建立資料表
<a name="LSIJavaDocumentAPI.CreateTableWithIndex"></a>

您必須同時建立資料表和本機次要索引。若要執行這項操作，請使用 `createTable` 方法，並提供一或多個本機次要索引的規格。以下 Java 程式碼範例會建立資料表來保存音樂收藏中歌曲的相關資訊。分割區索引鍵為 `Artist`，而排序索引鍵為 `SongTitle`。次要索引 `AlbumTitleIndex` 有助於依照專輯標題查詢。

以下是使用 DynamoDB Document API 建立具有本機次要索引的資料表的步驟。

1. 建立 `DynamoDB` 類別的執行個體。

1. 建立 `CreateTableRequest` 類別的執行個體，以提供請求資訊。

   您必須提供資料表名稱、其主索引鍵，以及佈建的輸送量數值。對於本機次要索引，您必須提供索引名稱、索引排序索引鍵的名稱和資料類型、索引的索引鍵結構描述以及屬性投影。

1. 以參數形式提供請求物件，以便呼叫 `createTable` 方法。

下列 Java 程式碼範例示範上述步驟。程式碼會建立一個資料表 (`Music`) 與次要索引 `AlbumTitle` 屬性。資料表分割區索引鍵和排序索引鍵以及索引排序索引鍵，是唯一投影到索引的屬性。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName);

//ProvisionedThroughput
createTableRequest.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits((long)5).withWriteCapacityUnits((long)5));

//AttributeDefinitions
ArrayList<AttributeDefinition> attributeDefinitions= new ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition().withAttributeName("Artist").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("SongTitle").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("AlbumTitle").withAttributeType("S"));

createTableRequest.setAttributeDefinitions(attributeDefinitions);

//KeySchema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
tableKeySchema.add(new KeySchemaElement().withAttributeName("SongTitle").withKeyType(KeyType.RANGE));  //Sort key

createTableRequest.setKeySchema(tableKeySchema);

ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>();
indexKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
indexKeySchema.add(new KeySchemaElement().withAttributeName("AlbumTitle").withKeyType(KeyType.RANGE));  //Sort key

Projection projection = new Projection().withProjectionType(ProjectionType.INCLUDE);
ArrayList<String> nonKeyAttributes = new ArrayList<String>();
nonKeyAttributes.add("Genre");
nonKeyAttributes.add("Year");
projection.setNonKeyAttributes(nonKeyAttributes);

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
    .withIndexName("AlbumTitleIndex").withKeySchema(indexKeySchema).withProjection(projection);

ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<LocalSecondaryIndex>();
localSecondaryIndexes.add(localSecondaryIndex);
createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes);

Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
```

您必須等到 DynamoDB 建立資料表，並將資料表狀態設定為 `ACTIVE`。之後，您可以開始將資料項目放入資料表中。

## 使用本機次要索引描述資料表
<a name="LSIJavaDocumentAPI.DescribeTableWithIndex"></a>

若要取得資料表上本機次要索引的資訊，請使用 `describeTable` 方法。對於每個索引，您可以存取其名稱、索引鍵結構描述和投影屬性。

以下是使用 適用於 Java 的 AWS SDK Document API 存取資料表的本機次要索引資訊的步驟。

1. 建立 `DynamoDB` 類別的執行個體。

1. 建立 `Table` 類別的執行個體。您必須提供資料表名稱。

1. 在 `Table` 物件上呼叫 `describeTable` 方法。

下列 Java 程式碼範例示範上述步驟。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);

TableDescription tableDescription = table.describe();

List<LocalSecondaryIndexDescription> localSecondaryIndexes 
    = tableDescription.getLocalSecondaryIndexes();

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.

Iterator<LocalSecondaryIndexDescription> lsiIter = localSecondaryIndexes.iterator();
while (lsiIter.hasNext()) {

    LocalSecondaryIndexDescription lsiDescription = lsiIter.next();
    System.out.println("Info for index " + lsiDescription.getIndexName() + ":");
    Iterator<KeySchemaElement> kseIter = lsiDescription.getKeySchema().iterator();
    while (kseIter.hasNext()) {
        KeySchemaElement kse = kseIter.next();
        System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType());
    }
    Projection projection = lsiDescription.getProjection();
    System.out.println("\tThe projection type is: " + projection.getProjectionType());
    if (projection.getProjectionType().toString().equals("INCLUDE")) {
        System.out.println("\t\tThe non-key projected attributes are: " + projection.getNonKeyAttributes());
    }
}
```

## 查詢本機次要索引
<a name="LSIJavaDocumentAPI.QueryAnIndex"></a>

您可以依照與 `Query` 資料表大致相同的方式在本機次要索引上使用 `Query` 操作。您必須指定索引名稱、索引排序索引鍵的查詢準則，以及您要傳回的屬性。在本例中，索引是 `AlbumTitleIndex`，而索引排序索引鍵為 `AlbumTitle`。

傳回的唯一屬性是已投影到索引的屬性。您也可以修改此查詢來選擇非索引鍵屬性，但這需要相對昂貴的資料表擷取活動。如需資料表擷取的詳細資訊，請參閱 [屬性投影](LSI.md#LSI.Projections)。

以下是使用 適用於 Java 的 AWS SDK 文件 API 查詢本機次要索引的步驟。

1. 建立 `DynamoDB` 類別的執行個體。

1. 建立 `Table` 類別的執行個體。您必須提供資料表名稱。

1. 建立 `Index` 類別的執行個體。您必須提供索引名稱。

1. 呼叫 `Index` 類別的 `query` 方法。

下列 Java 程式碼範例示範上述步驟。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);
Index index = table.getIndex("AlbumTitleIndex");

QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"));

ItemCollection<QueryOutcome> items = index.query(spec);

Iterator<Item> itemsIter = items.iterator();

while (itemsIter.hasNext()) {
    Item item = itemsIter.next();
    System.out.println(item.toJSONPretty());
}
```

### 本機次要索引上的一致讀取
<a name="LSIJavaDocumentAPI.ConsistentReads"></a>

與僅支援最終一致讀取的全域次要索引不同，本機次要索引支援最終一致和強烈一致讀取。來自本機次要索引的高度一致性讀取一律會傳回最新的更新值。如果查詢需要從基礎資料表擷取其他屬性，則這些擷取的屬性也與索引一致。

根據預設， `Query`會使用最終一致讀取。若要請求強式一致讀取，請在 `true`中將 `ConsistentRead`設定為 `QuerySpec`。下列範例查詢`AlbumTitleIndex`使用強式一致讀取：

**Example**  

```
QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"))
    .withConsistentRead(true);
```

**注意**  
強式一致讀取每傳回 4 KB 的資料會耗用一個讀取容量單位 （四捨五入），而最終一致讀取則會耗用一半。例如，傳回 9 KB 資料的強式一致讀取耗用 3 個讀取容量單位 (9 KB/4 KB = 2.25，四捨五入至 3)，而使用最終一致讀取的相同查詢耗用 1.5 個讀取容量單位。如果您的應用程式可以容忍讀取可能稍微過時的資料，請使用最終一致讀取來減少讀取容量用量。如需詳細資訊，請參閱[讀取容量單位](LSI.md#LSI.ThroughputConsiderations.Reads)。

# 範例：使用 Java Document API 的本機次要索引
<a name="LSIJavaDocumentAPI.Example"></a>

下列 Java 程式碼範例示範如何使用 Amazon DynamoDB 中的本機次要索引。例如，您可以建立名為 `CustomerOrders` 的資料表，其中分割區索引鍵為 `CustomerId`，排序索引鍵為 `OrderId`。此資料表上有兩個本機次要索引：
+ `OrderCreationDateIndex`：排序索引鍵為 `OrderCreationDate`，並且以下屬性會投影到索引：
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex`：排序索引鍵為 `IsOpen`，並且所有的資料表屬性都會投影到索引。

建立 `CustomerOrders` 資料表後，程式會載入所含資料表示客戶訂單的資料表。然後，其便會使用本機次要索引查詢資料。最後，程式會刪除 `CustomerOrders` 資料表。

如需測試下列範例的逐步說明，請參閱 [Java 程式碼範例](CodeSamples.Java.md)。

**Example**  

```
package com.example.dynamodb;

import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;

import java.util.HashMap;
import java.util.Map;

public class DocumentAPILocalSecondaryIndexExample {

    static DynamoDbClient client = DynamoDbClient.create();
    public static String tableName = "CustomerOrders";

    public static void main(String[] args) {
        createTable();
        loadData();
        query(null);
        query("IsOpenIndex");
        query("OrderCreationDateIndex");
        deleteTable(tableName);
    }

    public static void createTable() {
        CreateTableRequest request = CreateTableRequest.builder()
            .tableName(tableName)
            .provisionedThroughput(ProvisionedThroughput.builder()
                .readCapacityUnits(1L)
                .writeCapacityUnits(1L)
                .build())
            .attributeDefinitions(
                AttributeDefinition.builder().attributeName("CustomerId").attributeType(ScalarAttributeType.S).build(),
                AttributeDefinition.builder().attributeName("OrderId").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("OrderCreationDate").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("IsOpen").attributeType(ScalarAttributeType.N).build())
            .keySchema(
                KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                KeySchemaElement.builder().attributeName("OrderId").keyType(KeyType.RANGE).build())
            .localSecondaryIndexes(
                LocalSecondaryIndex.builder()
                    .indexName("OrderCreationDateIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("OrderCreationDate").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.INCLUDE)
                        .nonKeyAttributes("ProductCategory", "ProductName")
                        .build())
                    .build(),
                LocalSecondaryIndex.builder()
                    .indexName("IsOpenIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("IsOpen").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.ALL)
                        .build())
                    .build())
            .build();

        System.out.println("Creating table " + tableName + "...");
        client.createTable(request);

        try (DynamoDbWaiter waiter = client.waiter()) {
            WaiterResponse<DescribeTableResponse> response = waiter.waitUntilTableExists(r -> r.tableName(tableName));
            response.matched().response().ifPresent(System.out::println);
        }
    }

    public static void query(String indexName) {
        System.out.println("\n***********************************************************\n");
        System.out.println("Querying table " + tableName + "...");

        if ("IsOpenIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that are open.");
            System.out.println("Only a user-specified list of attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_isopen", AttributeValue.builder().n("1").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and IsOpen = :v_isopen")
                .expressionAttributeValues(values)
                .projectionExpression("OrderCreationDate, ProductCategory, ProductName, OrderStatus")
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else if ("OrderCreationDateIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that were placed after 01/31/2015.");
            System.out.println("Only the projected attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_orddate", AttributeValue.builder().n("20150131").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and OrderCreationDate >= :v_orddate")
                .expressionAttributeValues(values)
                .select(Select.ALL_PROJECTED_ATTRIBUTES)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else {
            System.out.println("\nNo index: All of Bob's orders, by OrderId:\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .keyConditionExpression("CustomerId = :v_custid")
                .expressionAttributeValues(values)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);
        }
    }

    public static void deleteTable(String tableName) {
        System.out.println("Deleting table " + tableName + "...");
        client.deleteTable(DeleteTableRequest.builder().tableName(tableName).build());

        try (DynamoDbWaiter waiter = client.waiter()) {
            waiter.waitUntilTableNotExists(r -> r.tableName(tableName));
        }
    }

    public static void loadData() {
        System.out.println("Loading data into table " + tableName + "...");

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150101").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("The Great Outdoors").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Bike").build(),
            "ProductName", AttributeValue.builder().s("Super Mountain").build(),
            "OrderStatus", AttributeValue.builder().s("ORDER RECEIVED").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150304").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("A Quiet Interlude").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("176493").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150111").build(),
            "ProductCategory", AttributeValue.builder().s("Movie").build(),
            "ProductName", AttributeValue.builder().s("Calm Before The Storm").build(),
            "OrderStatus", AttributeValue.builder().s("SHIPPING DELAY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("859323").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150124").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("E-Z Listening").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("756943").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("Symphony 9").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("645193").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("4").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150222").build(),
            "ProductCategory", AttributeValue.builder().s("Hardware").build(),
            "ProductName", AttributeValue.builder().s("Extra Heavy Hammer").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("5").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150309").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("How To Cook").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("440185").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("6").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150318").build(),
            "ProductCategory", AttributeValue.builder().s("Luggage").build(),
            "ProductName", AttributeValue.builder().s("Really Big Suitcase").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("893927").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("7").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150324").build(),
            "ProductCategory", AttributeValue.builder().s("Golf").build(),
            "ProductName", AttributeValue.builder().s("PGA Pro II").build(),
            "OrderStatus", AttributeValue.builder().s("OUT FOR DELIVERY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("383283").build()));
    }

    private static void putItem(Map<String, AttributeValue> item) {
        client.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
    }
}
```

# 使用本機次要索引：.NET
<a name="LSILowLevelDotNet"></a>

**Topics**
+ [使用本機次要索引建立資料表](#LSILowLevelDotNet.CreateTableWithIndex)
+ [使用本機次要索引描述資料表](#LSILowLevelDotNet.DescribeTableWithIndex)
+ [查詢本機次要索引](#LSILowLevelDotNet.QueryAnIndex)
+ [範例：使用 適用於 .NET 的 AWS SDK 低階 API 的本機次要索引](LSILowLevelDotNet.Example.md)

您可以使用 適用於 .NET 的 AWS SDK 低階 API 來建立具有一或多個本機次要索引的 Amazon DynamoDB 資料表、描述資料表上的索引，並使用索引執行查詢。這些操作會映射至對應的低階 DynamoDB API 動作。如需詳細資訊，請參閱 [.NET 程式碼範例](CodeSamples.DotNet.md)。

下列是使用 .NET 低階 API 執行資料表操作的一般步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 透過建立對應的請求物件，為操作提供必要及選用的參數。

   例如，建立 `CreateTableRequest` 物件來建立資料表，以及建立 `QueryRequest` 物件來查詢資料表或索引。

1. 執行您在前一步驟中建立之用戶端所提供的適當方法。

## 使用本機次要索引建立資料表
<a name="LSILowLevelDotNet.CreateTableWithIndex"></a>

您必須同時建立資料表和本機次要索引。若要執行這項操作，請使用 `CreateTable`，並提供一或多個本機次要索引的規格。以下 C\$1 程式碼範例會建立資料表來保存音樂收藏中歌曲的相關資訊。分割區索引鍵為 `Artist`，而排序索引鍵為 `SongTitle`。次要索引 `AlbumTitleIndex` 有助於依照專輯標題查詢。

以下是使用 .NET 低階 API 建立具有本機次要索引之資料表的步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 建立 `CreateTableRequest` 類別的執行個體，以提供請求資訊。

   您必須提供資料表名稱、其主索引鍵，以及佈建的輸送量數值。對於本機次要索引，您必須提供索引名稱、索引排序索引鍵的名稱和資料類型、索引的索引鍵結構描述以及屬性投影。

1. 以參數形式提供請求物件，以便執行 `CreateTable` 方法。

下列 C\$1 程式碼範例示範前述步驟。程式碼會建立一個資料表 (`Music`) 與次要索引 `AlbumTitle` 屬性。資料表分割區索引鍵和排序索引鍵以及索引排序索引鍵，是唯一投影到索引的屬性。

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest()
{
    TableName = tableName
};

//ProvisionedThroughput
createTableRequest.ProvisionedThroughput = new ProvisionedThroughput()
{
    ReadCapacityUnits = (long)5,
    WriteCapacityUnits = (long)5
};

//AttributeDefinitions
List<AttributeDefinition> attributeDefinitions = new List<AttributeDefinition>();

attributeDefinitions.Add(new AttributeDefinition()
{
    AttributeName = "Artist",
    AttributeType = "S"
});

attributeDefinitions.Add(new AttributeDefinition()
 {
     AttributeName = "SongTitle",
     AttributeType = "S"
 });

attributeDefinitions.Add(new AttributeDefinition()
 {
     AttributeName = "AlbumTitle",
     AttributeType = "S"
 });

createTableRequest.AttributeDefinitions = attributeDefinitions;

//KeySchema
List<KeySchemaElement> tableKeySchema = new List<KeySchemaElement>();

tableKeySchema.Add(new KeySchemaElement() { AttributeName = "Artist", KeyType = "HASH" });  //Partition key
tableKeySchema.Add(new KeySchemaElement() { AttributeName = "SongTitle", KeyType = "RANGE" });  //Sort key

createTableRequest.KeySchema = tableKeySchema;

List<KeySchemaElement> indexKeySchema = new List<KeySchemaElement>();
indexKeySchema.Add(new KeySchemaElement() { AttributeName = "Artist", KeyType = "HASH" });  //Partition key
indexKeySchema.Add(new KeySchemaElement() { AttributeName = "AlbumTitle", KeyType = "RANGE" });  //Sort key

Projection projection = new Projection() { ProjectionType = "INCLUDE" };

List<string> nonKeyAttributes = new List<string>();
nonKeyAttributes.Add("Genre");
nonKeyAttributes.Add("Year");
projection.NonKeyAttributes = nonKeyAttributes;

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
{
    IndexName = "AlbumTitleIndex",
    KeySchema = indexKeySchema,
    Projection = projection
};

List<LocalSecondaryIndex> localSecondaryIndexes = new List<LocalSecondaryIndex>();
localSecondaryIndexes.Add(localSecondaryIndex);
createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

CreateTableResponse result = client.CreateTable(createTableRequest);
Console.WriteLine(result.CreateTableResult.TableDescription.TableName);
Console.WriteLine(result.CreateTableResult.TableDescription.TableStatus);
```

您必須等到 DynamoDB 建立資料表，並將資料表狀態設定為 `ACTIVE`。之後，您可以開始將資料項目放入資料表中。

## 使用本機次要索引描述資料表
<a name="LSILowLevelDotNet.DescribeTableWithIndex"></a>

若要取得資料表上本機次要索引的資訊，請使用 `DescribeTable` API。對於每個索引，您可以存取其名稱、索引鍵結構描述和投影屬性。

以下是使用 .NET 低階 API 存取資料表的本機次要索引資訊的步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 建立 `DescribeTableRequest` 類別的執行個體，以提供請求資訊。您必須提供資料表名稱。

1. 以參數形式提供請求物件，以便執行 `describeTable` 方法。

下列 C\$1 程式碼範例示範前述步驟。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "Music";

DescribeTableResponse response = client.DescribeTable(new DescribeTableRequest() { TableName = tableName });
List<LocalSecondaryIndexDescription> localSecondaryIndexes =
    response.DescribeTableResult.Table.LocalSecondaryIndexes;

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.
foreach (LocalSecondaryIndexDescription lsiDescription in localSecondaryIndexes)
{
    Console.WriteLine("Info for index " + lsiDescription.IndexName + ":");

    foreach (KeySchemaElement kse in lsiDescription.KeySchema)
    {
        Console.WriteLine("\t" + kse.AttributeName + ": key type is " + kse.KeyType);
    }

    Projection projection = lsiDescription.Projection;

    Console.WriteLine("\tThe projection type is: " + projection.ProjectionType);

    if (projection.ProjectionType.ToString().Equals("INCLUDE"))
    {
        Console.WriteLine("\t\tThe non-key projected attributes are:");

        foreach (String s in projection.NonKeyAttributes)
        {
            Console.WriteLine("\t\t" + s);
        }

    }
}
```

## 查詢本機次要索引
<a name="LSILowLevelDotNet.QueryAnIndex"></a>

您可以依照與 `Query` 資料表大致相同的方式在本機次要索引上使用 `Query`。您必須指定索引名稱、索引排序索引鍵的查詢準則，以及您要傳回的屬性。在本例中，索引是 `AlbumTitleIndex`，而索引排序索引鍵為 `AlbumTitle`。

傳回的唯一屬性是已投影到索引的屬性。您也可以修改此查詢來選擇非索引鍵屬性，但這需要相對昂貴的資料表擷取活動。如需資料表擷取的詳細資訊，請參閱 [屬性投影](LSI.md#LSI.Projections)

以下是使用 .NET 低階 API 查詢本機次要索引的步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 建立 `QueryRequest` 類別的執行個體，以提供請求資訊。

1. 以參數形式提供請求物件，以便執行 `query` 方法。

下列 C\$1 程式碼範例示範前述步驟。

**Example**  

```
QueryRequest queryRequest = new QueryRequest
{
    TableName = "Music",
    IndexName = "AlbumTitleIndex",
    Select = "ALL_ATTRIBUTES",
    ScanIndexForward = true,
    KeyConditionExpression = "Artist = :v_artist and AlbumTitle = :v_title",
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":v_artist",new AttributeValue {S = "Acme Band"}},
        {":v_title",new AttributeValue {S = "Songs About Life"}}
    },
};

QueryResponse response = client.Query(queryRequest);

foreach (var attribs in response.Items)
{
    foreach (var attrib in attribs)
    {
        Console.WriteLine(attrib.Key + " ---> " + attrib.Value.S);
    }
    Console.WriteLine();
}
```

# 範例：使用 適用於 .NET 的 AWS SDK 低階 API 的本機次要索引
<a name="LSILowLevelDotNet.Example"></a>

下列 C\$1 程式碼範例示範如何在 Amazon DynamoDB 中使用本機次要索引。例如，您可以建立名為 `CustomerOrders` 的資料表，其中分割區索引鍵為 `CustomerId`，排序索引鍵為 `OrderId`。此資料表上有兩個本機次要索引：
+ `OrderCreationDateIndex`：排序索引鍵為 `OrderCreationDate`，並且以下屬性會投影到索引：
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex`：排序索引鍵為 `IsOpen`，並且所有的資料表屬性都會投影到索引。

建立 `CustomerOrders` 資料表後，程式會載入所含資料表示客戶訂單的資料表。然後，其便會使用本機次要索引查詢資料。最後，程式會刪除 `CustomerOrders` 資料表。

如需測試下列範例的逐步說明，請參閱 [.NET 程式碼範例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.SecurityToken;

namespace com.amazonaws.codesamples
{
    class LowLevelLocalSecondaryIndexExample
    {
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
        private static string tableName = "CustomerOrders";

        static void Main(string[] args)
        {
            try
            {
                CreateTable();
                LoadData();

                Query(null);
                Query("IsOpenIndex");
                Query("OrderCreationDateIndex");

                DeleteTable(tableName);

                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
            catch (AmazonDynamoDBException e) { Console.WriteLine(e.Message); }
            catch (AmazonServiceException e) { Console.WriteLine(e.Message); }
            catch (Exception e) { Console.WriteLine(e.Message); }
        }

        private static void CreateTable()
        {
            var createTableRequest =
                new CreateTableRequest()
                {
                    TableName = tableName,
                    ProvisionedThroughput =
                    new ProvisionedThroughput()
                    {
                        ReadCapacityUnits = (long)1,
                        WriteCapacityUnits = (long)1
                    }
                };

            var attributeDefinitions = new List<AttributeDefinition>()
        {
            // Attribute definitions for table primary key
            { new AttributeDefinition() {
                  AttributeName = "CustomerId", AttributeType = "S"
              } },
            { new AttributeDefinition() {
                  AttributeName = "OrderId", AttributeType = "N"
              } },
            // Attribute definitions for index primary key
            { new AttributeDefinition() {
                  AttributeName = "OrderCreationDate", AttributeType = "N"
              } },
            { new AttributeDefinition() {
                  AttributeName = "IsOpen", AttributeType = "N"
              }}
        };

            createTableRequest.AttributeDefinitions = attributeDefinitions;

            // Key schema for table
            var tableKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              } },                                                  //Partition key
            { new KeySchemaElement() {
                  AttributeName = "OrderId", KeyType = "RANGE"
              } }                                                //Sort key
        };

            createTableRequest.KeySchema = tableKeySchema;

            var localSecondaryIndexes = new List<LocalSecondaryIndex>();

            // OrderCreationDateIndex
            LocalSecondaryIndex orderCreationDateIndex = new LocalSecondaryIndex()
            {
                IndexName = "OrderCreationDateIndex"
            };

            // Key schema for OrderCreationDateIndex
            var indexKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              } },                                                    //Partition key
            { new KeySchemaElement() {
                  AttributeName = "OrderCreationDate", KeyType = "RANGE"
              } }                                                            //Sort key
        };

            orderCreationDateIndex.KeySchema = indexKeySchema;

            // Projection (with list of projected attributes) for
            // OrderCreationDateIndex
            var projection = new Projection()
            {
                ProjectionType = "INCLUDE"
            };

            var nonKeyAttributes = new List<string>()
        {
            "ProductCategory",
            "ProductName"
        };
            projection.NonKeyAttributes = nonKeyAttributes;

            orderCreationDateIndex.Projection = projection;

            localSecondaryIndexes.Add(orderCreationDateIndex);

            // IsOpenIndex
            LocalSecondaryIndex isOpenIndex
                = new LocalSecondaryIndex()
                {
                    IndexName = "IsOpenIndex"
                };

            // Key schema for IsOpenIndex
            indexKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              }},                                                     //Partition key
            { new KeySchemaElement() {
                  AttributeName = "IsOpen", KeyType = "RANGE"
              }}                                                  //Sort key
        };

            // Projection (all attributes) for IsOpenIndex
            projection = new Projection()
            {
                ProjectionType = "ALL"
            };

            isOpenIndex.KeySchema = indexKeySchema;
            isOpenIndex.Projection = projection;

            localSecondaryIndexes.Add(isOpenIndex);

            // Add index definitions to CreateTable request
            createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

            Console.WriteLine("Creating table " + tableName + "...");
            client.CreateTable(createTableRequest);
            WaitUntilTableReady(tableName);
        }

        public static void Query(string indexName)
        {
            Console.WriteLine("\n***********************************************************\n");
            Console.WriteLine("Querying table " + tableName + "...");

            QueryRequest queryRequest = new QueryRequest()
            {
                TableName = tableName,
                ConsistentRead = true,
                ScanIndexForward = true,
                ReturnConsumedCapacity = "TOTAL"
            };


            String keyConditionExpression = "CustomerId = :v_customerId";
            Dictionary<string, AttributeValue> expressionAttributeValues = new Dictionary<string, AttributeValue> {
            {":v_customerId", new AttributeValue {
                 S = "bob@example.com"
             }}
        };


            if (indexName == "IsOpenIndex")
            {
                Console.WriteLine("\nUsing index: '" + indexName
                          + "': Bob's orders that are open.");
                Console.WriteLine("Only a user-specified list of attributes are returned\n");
                queryRequest.IndexName = indexName;

                keyConditionExpression += " and IsOpen = :v_isOpen";
                expressionAttributeValues.Add(":v_isOpen", new AttributeValue
                {
                    N = "1"
                });

                // ProjectionExpression
                queryRequest.ProjectionExpression = "OrderCreationDate, ProductCategory, ProductName, OrderStatus";
            }
            else if (indexName == "OrderCreationDateIndex")
            {
                Console.WriteLine("\nUsing index: '" + indexName
                          + "': Bob's orders that were placed after 01/31/2013.");
                Console.WriteLine("Only the projected attributes are returned\n");
                queryRequest.IndexName = indexName;

                keyConditionExpression += " and OrderCreationDate > :v_Date";
                expressionAttributeValues.Add(":v_Date", new AttributeValue
                {
                    N = "20130131"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else
            {
                Console.WriteLine("\nNo index: All of Bob's orders, by OrderId:\n");
            }
            queryRequest.KeyConditionExpression = keyConditionExpression;
            queryRequest.ExpressionAttributeValues = expressionAttributeValues;

            var result = client.Query(queryRequest);
            var items = result.Items;
            foreach (var currentItem in items)
            {
                foreach (string attr in currentItem.Keys)
                {
                    if (attr == "OrderId" || attr == "IsOpen"
                        || attr == "OrderCreationDate")
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].N);
                    }
                    else
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].S);
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine("\nConsumed capacity: " + result.ConsumedCapacity.CapacityUnits + "\n");
        }

        private static void DeleteTable(string tableName)
        {
            Console.WriteLine("Deleting table " + tableName + "...");
            client.DeleteTable(new DeleteTableRequest()
            {
                TableName = tableName
            });
            WaitForTableToBeDeleted(tableName);
        }

        public static void LoadData()
        {
            Console.WriteLine("Loading data into table " + tableName + "...");

            Dictionary<string, AttributeValue> item = new Dictionary<string, AttributeValue>();

            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "1"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130101"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Book"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "The Great Outdoors"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "PACKING ITEMS"
            };
            /* no ShipmentTrackingId attribute */
            PutItemRequest putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "2"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130221"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Bike"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Super Mountain"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "ORDER RECEIVED"
            };
            /* no ShipmentTrackingId attribute */
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "3"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130304"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "A Quiet Interlude"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "IN TRANSIT"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "176493"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "1"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130111"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Movie"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Calm Before The Storm"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "SHIPPING DELAY"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "859323"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "2"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130124"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "E-Z Listening"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "756943"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "3"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130221"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Symphony 9"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "645193"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "4"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130222"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Hardware"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Extra Heavy Hammer"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "PACKING ITEMS"
            };
            /* no ShipmentTrackingId attribute */
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "5"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130309"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Book"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "How To Cook"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "IN TRANSIT"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "440185"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "6"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130318"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Luggage"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Really Big Suitcase"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "893927"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "7"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130324"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Golf"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "PGA Pro II"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "OUT FOR DELIVERY"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "383283"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);
        }

        private static void WaitUntilTableReady(string tableName)
        {
            string status = null;
            // Let us wait until table is created. Call DescribeTable.
            do
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                    status = res.Table.TableStatus;
                }
                catch (ResourceNotFoundException)
                {
                    // DescribeTable is eventually consistent. So you might
                    // get resource not found. So we handle the potential exception.
                }
            } while (status != "ACTIVE");
        }

        private static void WaitForTableToBeDeleted(string tableName)
        {
            bool tablePresent = true;

            while (tablePresent)
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                }
                catch (ResourceNotFoundException)
                {
                    tablePresent = false;
                }
            }
        }
    }
}
```

# 在 DynamoDB AWS CLI 中使用本機次要索引
<a name="LCICli"></a>

您可以使用 AWS CLI 建立含有一或多個本機次要索引的 Amazon DynamoDB 資料表、描述資料表上的索引，以及使用索引執行查詢。

**Topics**
+ [使用本機次要索引建立資料表](#LCICli.CreateTableWithIndex)
+ [使用本機次要索引描述資料表](#LCICli.DescribeTableWithIndex)
+ [查詢本機次要索引](#LCICli.QueryAnIndex)

## 使用本機次要索引建立資料表
<a name="LCICli.CreateTableWithIndex"></a>

您必須同時建立資料表和本機次要索引。若要執行這項操作，請使用 `create-table` 參數，並提供一或多個本機次要索引的規格。以下範例會建立資料表 (`Music`) 來保存音樂收藏中歌曲的相關資訊。分割區索引鍵為 `Artist`，而排序索引鍵為 `SongTitle`。`AlbumTitle` 屬性上的次要索引 `AlbumTitleIndex` 有助於依照專輯標題查詢。

```
aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions AttributeName=Artist,AttributeType=S AttributeName=SongTitle,AttributeType=S \
        AttributeName=AlbumTitle,AttributeType=S  \
    --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5 \
    --local-secondary-indexes \
        "[{\"IndexName\": \"AlbumTitleIndex\",
        \"KeySchema\":[{\"AttributeName\":\"Artist\",\"KeyType\":\"HASH\"},
                      {\"AttributeName\":\"AlbumTitle\",\"KeyType\":\"RANGE\"}],
        \"Projection\":{\"ProjectionType\":\"INCLUDE\",  \"NonKeyAttributes\":[\"Genre\", \"Year\"]}}]"
```

您必須等到 DynamoDB 建立資料表，並將資料表狀態設定為 `ACTIVE`。之後，您可以開始將資料項目放入資料表中。您可以使用 [describe-table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/describe-table.html) 來判斷資料表建立的狀態。

## 使用本機次要索引描述資料表
<a name="LCICli.DescribeTableWithIndex"></a>

若要取得資料表上本機次要索引的資訊，請使用 `describe-table` 參數。對於每個索引，您可以存取其名稱、索引鍵結構描述和投影屬性。

```
aws dynamodb describe-table --table-name Music
```

## 查詢本機次要索引
<a name="LCICli.QueryAnIndex"></a>

您可以依照與 `query` 資料表大致相同的方式在本機次要索引上使用 `query` 操作。您必須指定索引名稱、索引排序索引鍵的查詢準則，以及您要傳回的屬性。在本例中，索引是 `AlbumTitleIndex`，而索引排序索引鍵為 `AlbumTitle`。

傳回的唯一屬性是已投影到索引的屬性。您也可以修改此查詢來選擇非索引鍵屬性，但這需要相對昂貴的資料表擷取活動。如需資料表擷取的詳細資訊，請參閱 [屬性投影](LSI.md#LSI.Projections)。

```
aws dynamodb query \
    --table-name Music \
    --index-name AlbumTitleIndex \
    --key-condition-expression "Artist = :v_artist and AlbumTitle = :v_title" \
    --expression-attribute-values  '{":v_artist":{"S":"Acme Band"},":v_title":{"S":"Songs About Life"} }'
```