DynamoDB&Golang　Queryまとめ

Queryで使用するdynamodb/api.goに書かれたQueryInputは以下の通りです。

以下に、スペースを全て詰めた表を提供します。


|フィールド名|タイプ|説明|デフォルト値|SQLでの類似概念|
|---|---|---|---|---|
|*ConsistentRead|`*bool`|読み取りの整合性モデルを決定。`true`の場合、強い整合性の読み取りを使用。グローバルセカンダリインデックスではサポートされていない。|`false`|`SELECT` with `FOR UPDATE`|
|ExclusiveStartKey|`map[string]*AttributeValue`|評価を開始する最初のアイテムのプライマリキー。|`nil`|`OFFSET`|
|*ExpressionAttributeNames|`map[string]*string`|式内の属性名の代用トークン。|`nil`|`AS` (エイリアス)|
|*ExpressionAttributeValues|`map[string]*AttributeValue`|式内の属性値の代用トークン。|`nil`|プレースホルダー (`?`)|
|*FilterExpression|`*string`|`Query`操作後に適用される条件。|`nil`|`WHERE` (追加フィルター)|
|IndexName|`*string`|クエリを実行するインデックスの名前。|`nil`|`INDEX` (インデックス指定)|
|*KeyConditionExpression|`*string`|アイテムを取得するためのキー条件を指定する式。|`nil`|`WHERE` (主キー条件)|
|*Limit|`*int64`|評価する最大アイテム数。|`nil`|`LIMIT`|
|*ProjectionExpression|`*string`|取得する属性を指定する式。|`nil`|`SELECT`|
|ReturnConsumedCapacity|`*string`|レスポンスに含まれるスループット消費情報のレベルを決定。|`NONE`|なし|
|ScanIndexForward|`*bool`|インデックスの走査順序を決定。`true`の場合、昇順。|`true`|`ORDER BY`|
|*Select|`*string`|結果に含まれる属性を決定。|`ALL_ATTRIBUTES`|`SELECT`|
|*TableName|`*string`|アイテムを含むテーブルの名前。必須フィールド。|`nil`|`FROM`|


定義にAttributesToGet・ConditionalOperator・QueryFilter・KeyConditionsが含まれていたが、これらはレガシーパラメータのため使用されない模様。

# 表記方法
## TableName
これはそのままの意味でテーブルの名前を指定します。
```
input := &dynamodb.QueryInput{
    TableName: aws.String("TableName"),
}
```

## KeyConditionExpression&ExpressionAttributeValues
アイテムを取得するためのキー条件を指定する式で、SQLのWHERE句みたいなものです。
`ExpressionAttributevalues`で具体的な値を指定します。

KeyCondtionExpressionValuesで使用できる条件は以下の通りです。
| 条件                         | 説明                                                                |
|------------------------------|---------------------------------------------------------------------|
| 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 |

これらの条件を`KeyConditionExpression`で指定し`:hoge`で書かれた値を`ExpressionAttributeValues`でセットします。

例えば以下のようなデータがあるとします。
| PrimaryKey (OrderID) | SortKey (OrderDate) | CustomerName | Amount |
|----------------------|---------------------|--------------|--------|
| 1001                 | 2023-01-01          | Alice        | 50     |
| 1001                 | 2023-01-02          | Bob          | 30     |
| 1002                 | 2023-01-01          | Carol        | 70     |
| 1002                 | 2023-01-03          | Dave         | 40     |
| 1003                 | 2023-01-02          | Eve          | 20     |

このデータに対して以下のQueryを投げると、
```
input := &dynamodb.QueryInput{
    TableName: aws.String("Orders"),
    KeyConditionExpression: aws.String("OrderID = :pk AND OrderDate BETWEEN :start AND :end"),
    ExpressionAttributeValues: map[string]types.AttributeValue{
        ":pk": &types.AttributeValueMemberS{Value: aws.String("1002")},
        ":start": &types.AttributeValueMemberS{Value: aws.String("2023-01-01")},
        ":end": &types.AttributeValueMemberS{Value: aws.String("2023-01-03")},
    },
}
```
以下のようなデータを取得することができます。
これは`Orders`テーブルからPKが`1002`で、`2023-01-01`から`2023-01-03`のデータを取得するという意味だからです。
| OrderID | OrderDate  | CustomerName | Amount |
|---------|------------|--------------|--------|
| 1002    | 2023-01-01 | Carol        | 70     |
| 1002    | 2023-01-03 | Dave         | 40     |

## Limit
評価する最大アイテム数。
```
input := &dynamodb.QueryInput{
    TableName: aws.String("YourTableName"),
    Limit: aws.Int64(10),
}
```
指定した件数取得する設定です。
ただ、この設定はDynamoDBでは思った挙動にならないこともあるようで、使い方が意外と難しい模様です。
```
input := &dynamodb.QueryInput{
    TableName: aws.String("TableName"),
    Limit: aws.Int64(10),
}
```
例えば、以下の記事にあるように後述する`FilterExpression`を使用すると、これはQuery操作後に適用される条件のため10件取得と設定しても、10件取得したデータに対してFilterをかけてしまい思った挙動にならないようです。
https://www.denzow.me/entry/2018/02/04/130419

## FilterExpression
Limitの説明でも出てきましたが、Query操作後に適用される条件です。
`ExpressionAttributevalues`で具体的な値を指定します。
上述した通り、データ取得後にFilterExpressionが実行されるため、その点には注意が必要です。
```
    input := &dynamodb.ScanInput{
        TableName: aws.String("Orders"),
        FilterExpression: aws.String("Amount <= :amount"),
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":amount": &types.AttributeValueMemberN{Value: "40"},
        },
    }
```

## ProjectionExpression
取得したい特定の属性を指定するために使用します。
例えば、以下のようなテーブルがあるとしましょう。
| PrimaryKey (OrderID) | SortKey (OrderDate) | CustomerName | Amount |
|----------------------|---------------------|--------------|--------|
| 1001                 | 2023-01-01          | Alice        | 50     |
| 1001                 | 2023-01-02          | Bob          | 30     |
| 1002                 | 2023-01-01          | Carol        | 70     |
| 1002                 | 2023-01-03          | Dave         | 40     |
| 1003                 | 2023-01-02          | Eve          | 20     |

ここで以下のqueryで設定すると、
```
input := &dynamodb.ScanInput{
    TableName: aws.String("Orders"),
    ProjectionExpression: aws.String("OrderID, Amount"),
}
```
以下のように指定した属性のみ取得することが可能となります。
| OrderID | Amount |
|---------|--------|
| 1001    | 50     |
| 1001    | 30     |
| 1002    | 70     |
| 1002    | 40     |
| 1003    | 20     |

## SELECT
これは取得する属性を指定するという点で`ProjectionExpression`と非常に似ています。
そのため、`SELECT`と`ProjectionExpression`を同時に使用することはできません。ただし、SelectがSPECIFIC_ATTRIBUTESの場合は例外です。（ただ、一緒に使う意味がないので実際に同時に使うことはなさそう。）

というよりおそらく`ProjectionExpression`は`SELECT`のオプションの1つのよう（間違ってるかも。）

表でまとめると以下の通りとなります。
ただ、デフォルトで全ての属性を取得するので`ALL_ATTRIBUTES`は使わないだろうし、`SPECIFIC_ATTRIBUTES`も`Projection Expression`だけ指定すればいいから特に指定する必要はなさそうだし、あまり使う機会がなさそう。

唯一使えそうなのは`COUNT`くらいかな？

|オプション|説明|利用ケース|
|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
|**ALL_ATTRIBUTES**|指定されたテーブルまたはインデックスの全てのアイテム属性を返す。|全ての属性を取得したい場合に使用。|
|**ALL_PROJECTED_ATTRIBUTES**|インデックスをクエリする場合にのみ使用可能。インデックスにプロジェクトされた全ての属性を取得。|インデックスが全ての属性をプロジェクトするように設定されている場合に使用。|
|**COUNT**|一致するアイテムの数を返す。実際のアイテムそのものではない。|クエリの結果のアイテム数を知りたい場合に使用。|
|**SPECIFIC_ATTRIBUTES**|特定の属性のみを返す。`ProjectionExpression`と組み合わせて使用。|特定の属性のみを取得したい場合に使用。|
|**ProjectionExpression**|特定の属性を指定して取得する。|特定の属性のみを取得したい場合に使用。|

```
例えば、以下のように設定すれば検索結果のデータではなく、検索件数を取得できる。
input := &dynamodb.QueryInput{
    TableName: aws.String("Orders"),
    KeyConditionExpression: aws.String("OrderID = :id"),
    ExpressionAttributeValues: map[string]types.AttributeValue{
        ":id": &types.AttributeValueMemberS{Value: aws.String("1001")},
    },
    Select: types.SelectCount,
}
```

んー、でも結局これもQueryOutputの`ScannedCount`と`Count`フィールドがあるからそれを使えばいいから結局`SELECT`の使い道っていまいち分からない。。。😅

以下は`QueryOutput`のメモです。
|フィールド名|説明|
|----------------------|----------------------------------------------------------------|
| **Items**| クエリによって返されるアイテムのリスト。|
| **Count**| クエリによって返されたアイテムの数。|
| **ScannedCount** | フィルタリング前にスキャンされたアイテムの数。|
| **LastEvaluatedKey** | ページングが必要な場合に、次のページの開始キーを示す。|

## ExpressionAttributeNames
ExpressionAttributeNamesは、予約語や特殊文字を含む属性名のエイリアスを指定するために使用されます。SQLだと`AS`と同じようなものです。同じようなものです。

例えば以下のテーブル属性の`CustomerName`が予約語だったとしましょう。
| PrimaryKey (OrderID) | SortKey (OrderDate) | CustomerName | Amount |
|----------------------|---------------------|--------------|--------|
| 1001                 | 2023-01-01          | Alice        | 50     |
| 1001                 | 2023-01-02          | Bob          | 30     |
| 1002                 | 2023-01-01          | Carol        | 70     |
| 1002                 | 2023-01-03          | Dave         | 40     |
| 1003                 | 2023-01-02          | Eve          | 20     |

その場合、以下のように`#hoge`と書くことで`CustomerName`を使用することができます。
```
input := &dynamodb.ScanInput{
    TableName: aws.String("Orders"),
    ProjectionExpression: aws.String("OrderID, #n, Amount"),
    ExpressionAttributeNames: map[string]string{
        "#n": "CustomerName",
    },
}
```

## ConsistentRead
読み取りの整合性モデルを決定します。trueの場合、強い整合性の読み取りを使用。グローバルセカンダリインデックスではサポートされていません。
```
input := &dynamodb.QueryInput{
    TableName:      aws.String("YourTableName"),
    ConsistentRead: aws.Bool(true),
}
```

# まとめ
Queryで使用するオプションを今回は調べてまとめてみました。
頭が整理できてよかったです🙌