DynamoDBのQueryについてまとめてみました。 

#概要
DynamoDBを初めて触るにあたり、DynamoDBの特徴や設計思想をまとめたサイトはたくさんあったのですが、
クエリ(以下、「Query」)についてまとめたサイトが見当たらなかったため、備忘録としてまとめてみました。

#読書対象
・DynamoDBを初めて触るにあたり、あらかじめの概要は調査して理解できたが、具体的に操作するにはどすれば良いか不明確な方
・RDBについての前提知識がある方(基本的なクエリの書き方や用語について知識がある方)

#参考サイト
・AWS 公式ドキュメント
https://docs.aws.amazon.com/dynamodb/index.html

・gitにサンプルコードを公開しています
https://github.com/ryohei0109-develop/Amazon.DynamoDb.Sample

#用語について
###●検索方法について
####・Query
後術する「パーティションキー」を条件（WHERE句)として検索する方法です。

####・Scan
「パーティションキー」以外の属性を指定して検索する際に使用します。
「パーティションキー」を使わないため、検索速度はQueryに比べると遅いです。

###●プライマリキーについて
プライマリキーは、QueryでWHERE句に条件として指定できます。
プライマリキー以外をWHERE句として条件にしたい場合は、Scanで検索する必要があります。
####・Hash attribute
主キー、PK
「パーティションキー」とも呼ばれます。

####・Range attribute
主キーと合わせて複合主キーとして設定可能。
「ソートキー」とも呼ばれます。

####・Secondary Indexes
プライマリキーに対するクエリに加えて、代替キーを使用して、テーブル内のデータのクエリを実行可能。
QueryもしくはScanで検索可能。
以下2つの種類が存在する。
->Global Secondary Index
->Local Secondary Index

#型対応表
| JavaScrfipt | DynamoDB |
|:-----------:|:------------:|
| string | S |
| number | N |
| boolean | N |
| null | NULL |
| array | L |
| object | M |

#Queryの表記方法
SQL同様、テーブルを検索するQueryには様々な項目が設定できます。
(例) 「SELECT」、「WHERE」、「AS」等

また、各項目に設定する値は大文字・小文字が区別されるためご注意下さい。

###●Expression Attribute Names
SQLでいう「AS」にあたる部分です。
NoSQLでは予約語をエスケープしてそのまま使うことができないため、別の名称に置き換える必要があります。
置き換え後の名称の先頭は必ず「#」で始まる必要があります。
<pre>
// 例
// 「Name」属性名を「#n」という名称に置き換えて予約語重複の対策をします。
{
　　　　{ "#n", "Name" }
}
</pre>

###●Projection Expression
SQLでいう「SELECT」にあたる部分です。
取得属性名を配列で指定します。
Expression Attribute Names(AS句)で別名に置き換えている場合は、置き換え語の名称で指定します。
<pre>
// 例
{
　　　　[ "Id", "#Name", "Price" ]
}
</pre>

###●Key Condition Expression
SQLでいう「WHERE」句になります。(その1)
後術する「Expression Attribute Values」で設定した条件をプライマリキーに割り当てます。
指定できる種類は以下の通りです。
・a = b
　true if the attribute a is equal to the value b
・a < b
　true if a is less than b
・a <= b
　true if a is less than or equal to b
・a > b
　true if a is greater than b
・a >= b
　true if a is greater than or equal to b
・a BETWEEN b AND c
　true if a is greater than or equal to b, and less than or equal to c.
・begins_with (a, substr)
　true if the value of attribute a begins with a particular substring.

<pre>
// 例
// 「Expression Attribute Values」で設定した「:name」という条件を「ForumName」属性に適用します。
"ForumName = :name"
</pre>

###●Expression Attribute Values
SQLでいう「WHERE」句になります。(その2)
式の属性値はコロン (:) で始まり、1 つ以上の英数字が続きます。
作成した「Expression Attribute Values」は、前述の「Key Condition Expression」で呼び出して使用します。
<pre>
// 例
// 「:name」という名前で、String型文字列「Amazon DynamoDB」を条件として設定します。
{":name":{"S":"Amazon DynamoDB"}}
</pre>

###●Filter Expressions for Query
SQLでいう「WHERE」句になります。(その3)
QueryのWHERE句には通常「パーティションキー」しか使用できませんが、
「パーティションキー」以外の属性で条件を追加したい時にFilterを使います。
※逆にFilterには「パーティションキー」を指定できません。

1回のQueryで、最大1MBまでのデータを取得可能ですが、この制限は、Filterを評価する前に適用されます。
なので、DynamoDBのテーブル設計時は、「パーティションキー」で検索結果数を絞り込めるようにした方が良さそうです。

##Query例
参考サイト: https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Query.html
<pre>
// 例1
// ThreadテーブルからForumNameが"Amazon DynamoDB"のレコードを取得します。
// 「expression-attribute-values」で「:name」という名前で検索条件を設定し、それを「key-condition-expression」で呼び出して使用しています。
// ※expression-attribute-valuesにはパーティションキーしか指定できないためご注意下さい。
// SQL: `SELECT * FROM `Thread` WHERE `Name` = 'Amazon DynamoDB';

aws dynamodb query \
　--table-name Thread \
　--key-condition-expression "ForumName = :name" \
　--expression-attribute-values  '{":name":{"S":"Amazon DynamoDB"}}'
</pre>

<pre>
// 例2
// ThreadテーブルからForumNameが"Amazon DynamoDB"及びSubjectが"DynamoDB Thread 1"のレコードを取得します。
// 「expression-attribute-values」で「:name」、「:sub」という名前で検索条件を設定し、それを「key-condition-expression」で呼び出して使用しています。
// ※expression-attribute-valuesにはパーティションキーしか指定できないためご注意下さい。
// SQL: `SELECT * FROM `Thread` WHERE `Name` = 'Amazon DynamoDB' AND `Subject` = 'DynamoDB Thread 1';

aws dynamodb query \
　--table-name Thread \
　--key-condition-expression "ForumName = :name and Subject = :sub" \
　--expression-attribute-values  '{
　　":name":{"S":"Amazon DynamoDB"},
　　":sub":{"S":"DynamoDB Thread 1"}
　}'
</pre>

<pre>
// 例3
// ReplyテーブルからIdが"Amazon DynamoDB#DynamoDB Thread 1"かつReplyDateTimeが"2015-09"で始まるレコードを取得します。
// SQL: SELECT * FROM `Reply` WHERE `Id` = 'Amazon DynamoDB#DynamoDB Thread 1' AND `ReplyDateTime` LIKE '2015-09%';

aws dynamodb query \
　--table-name Reply \
　--key-condition-expression "Id = :id and begins_with(ReplyDateTime, :dt)" \
　--expression-attribute-values  '{
　　":id":{"S":"Amazon DynamoDB#DynamoDB Thread 1"},
　　":dt":{"S":"2015-09"}
　}'
</pre>

<pre>
// 例4 (Filter)
// Threadテーブルから以下の3つの条件で絞り込んで取得します。
// 条件1: `ForumName` = 'Amazon DynamoDB'
// 条件2: `Subject` = 'DynamoDB Thread 1'
　　上記2属性はプライマリキーなので「key-condition-expression」で指定します。
// 条件3: `Views`>= 3
// 　フィルター条件として「filter-expression」で指定しています。
// 　「Views」は予約語なので「#v」に置き換えています。

aws dynamodb query \
　--table-name Thread \
　--key-condition-expression "ForumName = :fn and Subject = :sub" \
　--filter-expression "#v >= :num" \
　--expression-attribute-names '{"#v": "Views"}' \
　--expression-attribute-values '{
　　":fn":{"S":"Amazon DynamoDB"},
　　":sub":{"S":"DynamoDB Thread 1"},
　　":num":{"N":"3"}
　}'
</pre>

#Queryの注意点
・Query の結果は常にソートキーの値によってソートされます。
ソートキーのデータ型が Number である場合は、結果が番号順で返されます。
その他の場合は、UTF-8 バイトの順序で結果が返されます。
デフォルトの並べ替え順序は昇順です。
順序を反転させるには、ScanIndexForward パラメータを false に設定します。

・1回のQueryオペレーションで、最大 1 MB のデータを取得できます。
この制限は、結果へのFilterExpressionの適用前に適用されます。
レスポンスにLastEvaluatedKeyが存在し、Null以外の場合、結果セットをページ分割する必要があります (AWS参照: <a href = "https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Query.Pagination.html">Paginating Table Query Results</a>を参照)。

・Queryは複合PKを設定しているテーブルにしか使用できません。
※パーティションキーの指定は必須のため、複合PK構造でないと必ず検索結果が1件になるため、
その場合はItemGet()メソッドを使用して下さい、ということのようです。

#アトミックカウンター
MySQL等のRDBにはAUTO INCREMENT機能があり、自動的にユニークな値を採番してPKとして使用できる機能がありますが、
DynamoDBにはデフォルトの機能としては存在しないため、同様の使用したい場合は自前で実装する必要があります。

(参照: AWS公式ドキュメント  https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters)