DynamoDB&Golang　データ取得/更新/削除/操作まとめ

# PutItem/UpdateItem/DeleteItem/BatchWriteItem/TransactWriteItems

アプリケーションを作成する上で非常によく使用する操作の違いや特徴をまとめてみました。

# 特徴の比較
| 特徴                    | GetItem                 | BatchGetItem            | TransactGetItem            |
|-------------------------|-------------------------|-------------------------|----------------------------|
| **用途**                | 単一アイテムの取得      | 複数アイテムの一括取得  | 複数の一貫したアイテム取得 |
| **最大アイテム数**      | 1つ                     | 100個まで               | 25個まで                   |
| **対応テーブル数**      | 1つのテーブル           | 複数のテーブル          | 複数のテーブル             |
| **強い整合性**          | オプション              | オプション              | デフォルトで強い整合性     |
| **トランザクション**    | サポートしない          | サポートしない          | サポートする               |
| **未処理アイテムの再試行** | 不要                   | 必要な場合がある         | 不要                        |


| 特徴                     | PutItem                   | UpdateItem               | DeleteItem                | BatchWriteItem            | TransactWriteItems        |
|--------------------------|---------------------------|--------------------------|---------------------------|---------------------------|---------------------------|
| **用途**                 | アイテムの追加または更新  | 既存アイテムの更新       | アイテムの削除            | 複数アイテムの一括追加または削除 | 複数のアイテム操作のトランザクション |
| **最大アイテム数**       | 1つ                       | 1つ                      | 1つ                       | 25個まで                   | 25個まで                   |
| **対応操作**             | Put (追加/更新)           | Update (更新)            | Delete (削除)             | Put, Delete (追加/削除)    | Put, Update, Delete, ConditionCheck |
| **トランザクション**     | サポートしない            | サポートしない           | サポートしない            | サポートしない             | サポートする               |
| **未処理アイテムの再試行** | 不要                      | 不要                     | 不要                      | 必要な場合がある           | 不要                       |


# 各メソッドの説明と使用例
## GetItem
用途: 単一のアイテムを取得する。
特徴: シンプルで高速。プライマリキーを使用してアイテムを取得する。
使用例: ユーザー情報をユーザーIDで取得する場合。
```
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    // DynamoDBクライアントを作成
    svc := dynamodb.NewFromConfig(cfg)

    // テーブル名とキーを設定
    tableName := "YourTableName"
    key := map[string]types.AttributeValue{
        "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
    }

    // GetItemInputの作成
    input := &dynamodb.GetItemInput{
        TableName: aws.String(tableName),
        Key:       key,
    }

    // GetItem APIの呼び出し
    result, err := svc.GetItem(context.TODO(), input)
    if err != nil {
        log.Fatalf("GetItem failed: %v", err)
    }

    if result.Item == nil {
        log.Println("No item found")
        return
    }

    fmt.Printf("Item: %v\n", result.Item)
}
```
## BatchGetItem
用途: 複数のアイテムを一度に取得する。
特徴: 最大100個のアイテムを複数のテーブルから一括取得可能。未処理アイテムがある場合は再試行が必要。
使用例: 一度に複数のユーザー情報を取得する場合。

```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"
    items := []types.TransactGetItem{
        {
            Get: &types.Get{
                TableName: aws.String(tableName),
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
                },
            },
        },
        {
            Get: &types.Get{
                TableName: aws.String(tableName),
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key2")},
                },
            },
        },
    }

    input := &dynamodb.TransactGetItemsInput{
        TransactItems: items,
    }

    result, err := svc.TransactGetItems(context.TODO(), input)
    if err != nil {
        log.Fatalf("TransactGetItems failed: %v", err)
    }

    for _, item := range result.Responses {
        log.Printf("Item: %v", item.Item)
    }
}
```

## TransactGetItem
用途: トランザクションとして複数のアイテムを一貫して取得する。
特徴: 最大25個のアイテムを複数のテーブルから一括取得可能。全ての操作が成功するか、全て失敗する。
使用例: 複数の関連データを一度に取得する場合。


```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"
    items := []types.TransactGetItem{
        {
            Get: &types.Get{
                TableName: aws.String(tableName),
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
                },
            },
        },
        {
            Get: &types.Get{
                TableName: aws.String(tableName),
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key2")},
                },
            },
        },
    }

    input := &dynamodb.TransactGetItemsInput{
        TransactItems: items,
    }

    result, err := svc.TransactGetItems(context.TODO(), input)
    if err != nil {
        log.Fatalf("TransactGetItems failed: %v", err)
    }

    for _, item := range result.Responses {
        log.Printf("Item: %v", item.Item)
    }
}
```
## PutItem
用途: アイテムを追加または更新します。
特徴: 単一のアイテムを対象とし、存在しない場合は新規追加、存在する場合は上書きします。
```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"
    item := map[string]types.AttributeValue{
        "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
        "Attribute":  &types.AttributeValueMemberS{Value: aws.String("Value1")},
    }

    input := &dynamodb.PutItemInput{
        TableName: aws.String(tableName),
        Item:      item,
    }

    _, err = svc.PutItem(context.TODO(), input)
    if err != nil {
        log.Fatalf("PutItem failed: %v", err)
    }

    log.Println("PutItem succeeded")
}
```

## UpdateItem
用途: 既存のアイテムを更新します。
特徴: 単一のアイテムの特定の属性を更新します。
```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"
    key := map[string]types.AttributeValue{
        "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
    }

    input := &dynamodb.UpdateItemInput{
        TableName: aws.String(tableName),
        Key:       key,
        UpdateExpression: aws.String("SET Attribute = :value"),
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":value": &types.AttributeValueMemberS{Value: aws.String("UpdatedValue")},
        },
    }

    _, err = svc.UpdateItem(context.TODO(), input)
    if err != nil {
        log.Fatalf("UpdateItem failed: %v", err)
    }

    log.Println("UpdateItem succeeded")
}
```

### UpdateItem追加説明

| アクション | 説明 |
|:-:|:-:|
| SET | 1つ以上の属性とその値をアイテムに追加。存在しない属性の場合、新しくその属性が作成される。既に存在する場合は値を置き換える。数値属性に対して値を加算または減算できる。<br>関数：<br> - if_not_exists(path, operand)：指定されたパスに属性が存在しない場合に operand を評価する。<br> - list_append(operand, operand)：リストに新しい要素を追加する。 |
| REMOVE | 1つ以上の属性をアイテムから削除。 |
| ADD | 指定された値をアイテムに追加。属性が存在する場合は数値の加算またはセットへの追加を行う。数値とセットデータ型のみサポート。トップレベルの属性にのみ使用可能。 |
| DELETE | セットから要素を削除。指定した値を既存のセットから引く。セットデータ型のみサポート。トップレベルの属性にのみ使用可能。 |

`UpdateExpression`は、どの属性にどのような操作を行うかを定義する式です。例えば、属性を追加、削除、または変更する操作を指定します。

そして、`ExpressionAttributeValues`と続けて使用していますが、このマップは、`UpdateExpression`内で使用されるプレースホルダー（今回だと`:c`の部分）に対応する実際の値を提供します。

つまり、以下の部分では、指定されたitem.ID を持つDynamoDBのアイテムを見つけ、そのアイテムの Content 属性を :c として指定された値（この場合は item.Content）に更新します。
```
input := &dynamodb.UpdateItemInput{
	TableName: aws.String(tableName),
	Key: map[string]*dynamodb.AttributeValue{
		"id": {
			S: aws.String(item.ID),
		},
	},
	UpdateExpression: aws.String("set Content = :c"),
	ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
		":c": {
			S: aws.String(item.Content),
		},
	},
}	
```

### SET
ちなみに、以下のようにまとめてSETして更新することもできます。
```
UpdateExpression: aws.String("set Content = :c, OtherAttribute = :o"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":c": {
        S: aws.String(item.Content),
    },
    ":o": {
        S: aws.String(item.OtherAttribute),
    },
}
```

他にも数値の`加算`や`減算`もできるようです。
例えば以下の例だとすでにあるAge属性にプレースホルダー`:v`を加算するよう`set`していて、具体的な値を`ExpressionAttributeValues`でvにN(Number)で1を渡しています。

つまり、既存のage属性に+１して更新するような書き方となります。
数値なのに`aws.String`っていうのが若干気持ち悪いなと思いますが、DynamoDBでは内部的に数値を文字列として扱うためらしいです。。。
```
UpdateExpression: aws.String("set Age = Age + :v"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":v": {
        N: aws.String("1"),
    },
}
```

あとは、`list_append`のような関数が用意されていてそれを使えば、例えばリストに新しい要素を加えることができます。

以下の場合は、Tagsというリストにプレースホルダー`:t`を追加しています。
具体的な値は`ExpressionAttributeValues`に書かれていて、`:t`に対して`L(リスト型)`で"NewTag"という文字列を追加しています。

```
UpdateExpression: aws.String("set Tags = list_append(Tags, :t)"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":t": {
        L: []dynamodb.AttributeValue{
            {S: aws.String("NewTag")},
        },
    },
}
```

ついでに、残りのアクションも見ておきます。
### REMOVE　属性を取り除く
まず`remove`ですが、これは単純でアイテムから1つ以上の属性を取り除きます。`delete`と混同しそうですが、後述するように`delete`の場合はセットから要素を削除するので、属性を取り除くわけでありません。
```
UpdateExpression: aws.String("remove desginatedAttribute")
```

`set`と同様に複数をまとめてremoveすることもできます
```
UpdateExpression: aws.String("remove Attribute1, Attribute2")
```
### ADD　数値かセットデータのみ
続いて、`add`ですが、`数値`と`セットデータ型`の属性のみ使用できます。

- _数値の場合_
`属性が存在する場合`、指定した値が既存の値に加算されますが、`属性が存在しない場合`は、0とみなし加算されます。

例えば、以下の場合、Score属性が存在しない場合は10となり、既にScore属性に仮に5が入っている場合、結果は15となります。
```
UpdateExpression: aws.String("ADD Score :inc"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":inc": {
        N: aws.String("10"),
    },
}
```

- _セットデータの場合_
`["42.2", "-19", "7.5", "3.14"]`のようなセットに対して追加することができます。

以下の場合、 次のような形のセットデータが既に存在している場合は、`"SS": ["something", "something"]`このセットデータに`SS（String Set）`でnewTagのように指定して追加することができます。

また、属性が存在しない場合は、セットは空のセットから始まります。
```
UpdateExpression: aws.String("ADD Tags :newTag"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":newTag": {
        SS: []*string{aws.String("urgent"), aws.String("important")},
    },
}
```
余談ですが、リストとセットはどちらも配列（のように）値を保持することができますが、`リストは同じ値を保持できる`一方で`セットは同じ値を保持できない`です。

もちろん、以下のように`NS（Numbers Set）`で追加することも可能です。
```
UpdateExpression: aws.String("add Numbers :n"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":n": {
        NS: []*string{"3"},
    },
}
```
### DELETE　セットデータから要素を取り除く
最後に`delete`ですが、前述しましたが、こちらは属性の削除ではなく、セットから要素を取り除きます。
つまり、`add`の逆だと考えると分かりやすいですね。ただ数値や文字列、バイナリなど他のデータ型には使用できないので注意です。

例えば、以下の場合は"TagToRemove"という文字列を`Tags`というセットデータ型の属性から削除しています。
```
UpdateExpression: aws.String("delete Tags :t"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":t": {
        SS: []string{"TagToRemove"},
    },
}
```

つまり、例えばTags属性が`["Tag1","Tag2", "TagsToRemove"]`だとした場合、このdeleteを実行すると`["Tag1","Tag2"]`になるということですね。

### おまけ
また`delete`に限ったことではないですが、他のアクションと組み合わせて使用することもできます。
例えば、以下のように`set`と`remove`を組み合わせて使ったりすることも可能です。
```
UpdateExpression: aws.String("set Content = :c remove ObsoleteAttribute"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
    ":c": {
        S: aws.String("Updated Content"),
    },
}
```

## DeleteItem
用途: アイテムを削除します。
特徴: 単一のアイテムを削除します。
```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"
    key := map[string]types.AttributeValue{
        "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
    }

    input := &dynamodb.DeleteItemInput{
        TableName: aws.String(tableName),
        Key:       key,
    }

    _, err = svc.DeleteItem(context.TODO(), input)
    if err != nil {
        log.Fatalf("DeleteItem failed: %v", err)
    }

    log.Println("DeleteItem succeeded")
}
```
## BatchWriteItem
用途: 複数のアイテムを一括で追加または削除します。
特徴: 最大25個のPutItemまたはDeleteItemリクエストを一度に実行します。
```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"
    writeRequests := []types.WriteRequest{
        {
            PutRequest: &types.PutRequest{
                Item: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
                    "Attribute":  &types.AttributeValueMemberS{Value: aws.String("Value1")},
                },
            },
        },
        {
            DeleteRequest: &types.DeleteRequest{
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key2")},
                },
            },
        },
    }

    input := &dynamodb.BatchWriteItemInput{
        RequestItems: map[string][]types.WriteRequest{
            tableName: writeRequests,
        },
    }

    _, err = svc.BatchWriteItem(context.TODO(), input)
    if err != nil {
        log.Fatalf("BatchWriteItem failed: %v", err)
    }

    log.Println("BatchWriteItem succeeded")
}
```

## TransactWriteItems
用途: 複数のアイテム操作をトランザクションとして一貫して実行します。
特徴: 最大25個のPutItem、UpdateItem、DeleteItem、ConditionCheckリクエストを一度に実行します。
```
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2"))
    if err != nil {
        log.Fatalf("unable to load SDK config, %v", err)
    }

    svc := dynamodb.NewFromConfig(cfg)

    tableName := "YourTableName"

    transactItems := []types.TransactWriteItem{
        {
            Put: &types.Put{
                TableName: &tableName,
                Item: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key1")},
                    "Attribute":  &types.AttributeValueMemberS{Value: aws.String("Value1")},
                },
            },
        },
        {
            Update: &types.Update{
                TableName: &tableName,
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key2")},
                },
                UpdateExpression: aws.String("SET Attribute = :value"),
                ExpressionAttributeValues: map[string]types.AttributeValue{
                    ":value": &types.AttributeValueMemberS{Value: aws.String("UpdatedValue")},
                },
            },
        },
        {
            Delete: &types.Delete{
                TableName: &tableName,
                Key: map[string]types.AttributeValue{
                    "PrimaryKey": &types.AttributeValueMemberS{Value: aws.String("Key3")},
                },
            },
        },
    }

    input := &dynamodb.TransactWriteItemsInput{
        TransactItems: transactItems,
    }

    _, err = svc.TransactWriteItems(context.TODO(), input)
    if err != nil {
        log.Fatalf("TransactWriteItems failed: %v", err)
    }

    log.Println("TransactWriteItems succeeded")
}
```