DynamoDBの条件付き Putでハマったので整理しておく

## はじめに
会社でDynamoDBの条件付き書き込みを使う機会があったのですが、数時間ハマってしまったので今後のためにも整理したいと思います。

## Condition Expressionsとは

- DynamoDBではデータの追加・更新・削除を行いたい場合に、PutItem, UpdateItem, DeleteItemなどを利用する
- これらの処理を行う際に条件を指定することができる
- 条件に一致する場合は処理を実施し、一致しない場合は処理を行わない

## Condition Putとは
- PutItemは以下の処理を行う
    - DynamoDB内に同じキーが存在しない場合、データを新規追加する
    - **DynamoDB内に同じキーが存在する場合、データを上書きする**

### 具体例(データ上書きの場合)
以下のテーブルが存在すると仮定します。

|Partition Key|Sort Key|Attribute|
|:----|:----|:----|
|Id|Price|ProductCategory|
|123|650|Sporting Goods|

上記のテーブルは以下を表しています。

- Id (Partition Key): 123
- Price (Sort Key): 650
- ProductCategory (Attribute): Sporting Goods

上記のテーブルに対して、以下のようにPut処理を行うと、テーブル内のデータが上書きされてしまいます。

```bash
aws dynamodb put-item \
    --table-name ProductCatalog \
    --item file://item.json \
```

```item.json
{
    "Id": {
        "N": "123"
    },
    "Price": {
        "N": "650"
    },
    "ProductCategory": {
        "S": "Sporting Goods"
    }
}
```

なぜなら、キーであるIdとPriceが同じ値だからです。
上書きしたくない場合は、以下のようにcondition-expressionを追加します。

```bash
aws dynamodb put-item \
    --table-name ProductCatalog \
    --item file://item.json \
    --condition-expression "attribute_not_exists(Id)"
```

## ハマったこと: プライマリキーが同じであれば条件付き putが動作すると思った

以下のCondition Expressionをみれば分かりますが、`attribute_not_exists(Id)`とあるので、てっきりIdがテーブル内に存在していれば動作すると思っていました。

```bash
condition-expression "attribute_not_exists(Id)"
```

そこで以下を試しました。

### テーブルの状態

|Partition Key|Sort Key|Attribute|
|:----|:----|:----|
|Id|Price|ProductCategory|
|123|650|Sporting Goods|

### コマンド
```bash
aws dynamodb put-item \
    --table-name ProductCatalog \
    --item file://item.json \
    --condition-expression "attribute_not_exists(Id)"
```

item.jsonはIdはテーブルと同じで、Priceは違う値としました。

```item.json
{
    "Id": {
        "N": "123"
    },
    "Price": {
        "N": "550"
    },
    "ProductCategory": {
        "S": "New Sporting Goods"
    }
}
```

### 結果
条件付きputが効かず、以下のようにデータが保存されました。

|Partition Key|Sort Key|Attribute|
|:----|:----|:----|
|Id|Price|ProductCategory|
|123|650|Sporting Goods|
|123|550|New Sporting Goods|

### 理由

公式ドキュメントを読むと、以下の記載がありました。

```
The PutItem operation overwrites an item with the same key (if it exists).
If you want to avoid this, use a condition expression.
This allows the write to proceed only if the item in question does not already have the same key.
```
Ref:https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html

意味としては以下です。

- PutItem は同じキーの項目が存在する場合に条件付きputが機能する
- 同じキーの項目が存在しない場合はデータが書き込まれる

つまり、条件付き putを使用する場合はputしようとするデータのキーがDynamoDB内のキーと一致している必要があります。
上記の例ではキーは`Id`と`Price`なので、これらが一致していないといけません。

## 検証内容まとめ

他のパターンもいろいろ試した結果をまとめると以下になります。
上記のハマったケースは、以下のパターン3になります。
以下の通り、キーが同じ場合に条件付き Putが動作します。
それ以外の場合は条件付きPutは動作しません。

|パターン|PK:Id|SK:Price|ProductCategory|condition-expression|結果:条件付きPut動作有無|
|:----|:----|:----|:----|:----|:----|
|1|同じ|同じ|異なる|attribute_not_exists(Id)|動作する|
|2|同じ|同じ|異なる|attribute_not_exists(Price)|動作する|
|3|同じ|異なる|異なる|attribute_not_exists(Id)|動作しない|
|4|同じ|異なる|同じ|attribute_not_exists(Id)|動作しない|
|5|同じ|異なる|同じ|attribute_not_exists(ProductCategory)|動作しない|

## まとめ
DynamoDBの条件付きputでハマったので整理しました。
条件付きputを使用する際は投入するデータのキーとDynamoDB内のデータのキーが同じ場合にしか動作しないことを覚えておくと良さそうです。

## 参考
- https://stackoverflow.com/questions/32833351/dynamodb-put-item-if-hash-or-hash-and-range-combination-doesnt-exist
- https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html