DynamoDBで条件に一致するレコードをまとめて削除する。

GUIでは一覧表示から全件チェックして削除できるが1度に実施するコマンドはない。
おそらくget-itemし表示しているデータを片っ端からdelete-itemしているのではなかろうか。
前方一致したレコードを全て削除する機会があり手でぽちぽちするのも面倒なので調べてみた。


# そもそものputとdelete(CLI)

## put-item

* 数字もクオーテーションでくくる。
* jsonも入れられる。ただ、クオートをエスケープする必要はある。

```sh
$ aws dynamodb put-item --table-name test --item '{"part":{"S":"hoge"}, "num":{"N":"1"}, "obj":{"S":"{\"test\": \"aaa\"}"}}'
```

* 同じパーティションキーで叩くとエラーはしないで勝手に上書きされる。
* そもそもレスポンスは何もない。
* 返値が欲しいときはオプションで色々指定できる。

```sh
$ aws dynamodb put-item --table-name test --item '{"part":{"S":"hoge2"}, "num":{"N":"2"}, "obj":{"S":"{\"test\": \"aaa\"}"}}' --return-consumed-capacity TOTAL
{
    "ConsumedCapacity": {
        "TableName": "test",
        "CapacityUnits": 1.0
    }
}
```

## delete-item

* `--key`は必須。~~削除対象に該当するキーを一つ書いておけばいい？~~削除対象を指定
* `--key`がそもそも条件に当てはまっていないとエラーする → あとからわかったが当然
* このコマンドの条件は期待した条件に一致するものを全て削除するものではない。
* `--expression-attribute-values`ではファイルを指定しても直接書いてもいい
* `--condition-expression`にダイレクトに書くと怒られる
* 条件に`begins_with`などを指定する場合これらは関数としての記法を取る
→ https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html

```sh:OK
$ aws dynamodb delete-item --table-name test --key '{"part":{"S":"hoge"}}' --condition-expression "part = :part" --expression-attribute-values file://values.json

$ aws dynamodb delete-item --table-name test --key '{"part":{"S":"hoge"}}' --condition-expression "part = :part" --expression-attribute-values '{":part": {"S": "hoge"}}'

$ aws dynamodb delete-item --table-name test --key '{"part":{"S":"hoge"}}' --condition-expression "begins_with (part, :part)" --expression-attribute-values '{":part": {"S": "hoge"}}'
```

```json:./values.json
{
    ":part": {"S": "hoge"}
}
```

```sh:NG
$ aws dynamodb delete-item --table-name test --key '{"part":{"S":"hoge"}}' --condition-expression "part = '{\"S\": \"hoge\"}'"
An error occurred (ValidationException) when calling the DeleteItem operation: Invalid ConditionExpression: Syntax error; token: "'", near: "= '{"

$ aws dynamodb delete-item --table-name test --key '{"part":{"S":"fuga"}}' --condition-expression "part = :part" --expression-attribute-values '{":part": {"S": "hoge"}}'
An error occurred (ConditionalCheckFailedException) when calling the DeleteItem operation: The conditional request failed
```

→ やりたいことはできないらしいので自分で組む必要があるっぽい。

# 条件に合致するデータを全件削除(boto3+lambda)

[この記事](https://tgctkkz.com/802)を参考

```python
import json
import boto3
from boto3.dynamodb.conditions import Key, Attr

TABLENAME='test'
dynamodb = boto3.client('dynamodb')

# 条件に一致するレコードを全て取得
def getRecords(flt, val):
    records = []
    ExclusiveStartKey = None
    while True:
        if ExclusiveStartKey == None:
            response = dynamodb.scan(
                TableName=TABLENAME, 
                FilterExpression=flt,
                ExpressionAttributeValues=val,
                Limit=2 # テストとして上限を超えるケースを起こしてみるため2を設定
                )
        else:
            response = dynamodb.scan(
                TableName=TABLENAME, 
                FilterExpression=flt,
                ExpressionAttributeValues=val,
                Limit=2, # テストとして上限を超えるケースを起こしてみるため2を設定
                ExclusiveStartKey=ExclusiveStartKey
                )
        for res in response["Items"]:
            records.append(res)
        if "LastEvaluatedKey" in response:
            ExclusiveStartKey = response["LastEvaluatedKey"]
        else:
            break
    return records

# 指定条件のレコードを削除    
def deleteRecords(flt):
    dynamodb.delete_item(
        TableName=TABLENAME,
        Key=flt
        )
    return 0 # エラーハンドリングしたい

def lambda_handler(event, context):
    targets = getRecords(
        'begins_with(part, :part)',
        {":part": {"S": "hoge"}}, # eventでパラメータを受け取るなどしたら汎用性上がりそう
        )
    print("get records")
    print(targets)
    
    for target in targets:
        print(target["part"]["S"])
        res = deleteRecords(
            {'part': target["part"]}
            )
    
    return 0
```

ただ、この方法では削除1回1回にリクエストが発生するので一括してできる方法を検討したい。
なお`begins_with`はqueryでは指定できなそうなのでscanを使用した。

```python
import json
import boto3
from boto3.dynamodb.conditions import Key, Attr

d = boto3.resource('dynamodb')
table = d.Table('test')
def lambda_handler(event, context):
    g = []
    ExclusiveStartKey = None
    while True:
        if ExclusiveStartKey == None:
            response = table.query(
                KeyConditionExpression=Key("part").begins_with("hoge"),
                Limit=2
            )
        else:
            response =  table.query(KeyConditionExpression="part",Limit=2, ExclusiveStartKey=ExclusiveStartKey)
        g.extend(response["Items"])
        if ("LastEvaluatedKey" in response) == True:
            ExclusiveStartKey = response["LastEvaluatedKey"]
        else:
            break
    print(g)
```

> "errorMessage": "An error occurred (ValidationException) when calling the Query operation: Query key condition not supported"

# 参考

* [Python boto3 DynamoDBのscanでフィルターを指定する方法](https://tgctkkz.com/802)
* [PythonでDynamoDBのQueryを実行する](https://zoe6120.com/2019/02/20/503/)
* [[AWS]boto3で、DynamoDBにクエリをかけるとき、制限件数以上のデータを最後まで取得する](http://blog.livedoor.jp/sce_info3-craft/archives/9431327.html)