Go
AWS
golang
DynamoDB

DynamoDBからデータを取得

こんな感じの、毎日名前が変えられる謎のUserNameテーブルがあるとします。
DynamoDB · AWS Console.png

パーティションキーとソートキー

IDとDate(データ登録日)によってユニークが決まるようなテーブルの場合、
IDをパーティションキー、Dateをソートキーと設定するといいようです。
http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GuidelinesForTables.html

GetItem

GetItemを使用すると、ユニークキーを指定してデータが1つ取得できます。
http://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/#DynamoDB.GetItem

以下の例では、dynamodbから取得後、User構造体に入れたりJsonにしたりして遊んでます。

dynamodb-get-item.go
package main

import (
    "encoding/json"
    "fmt"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

type User struct {
    UserID int    `json:"user_id" dynamodbav:"UserID"`
    Date   string `json:"date" dynamodbav:"Date"`
    Time   string `json:"time" dynamodbav:"Time"`
    Name   string `json:"name" dynamodbav:"Name"`
}

func main() {
    svc := dynamodb.New(session.New(), aws.NewConfig().WithRegion("ap-northeast-1"))

    input := &dynamodb.GetItemInput{
        TableName: aws.String("UserName"),
        Key: map[string]*dynamodb.AttributeValue{
            "UserID": {
                N: aws.String("1"),
            },
            "Date": {
                S: aws.String("20171215"),
            },
        },
    }

    result, err := svc.GetItem(input)
    if err != nil {
        fmt.Println("[GetItem Error]", err)
        return
    }

    user := &User{}
    if err := dynamodbattribute.UnmarshalMap(result.Item, user); err != nil {
        fmt.Println("[Unmarshal Error]", err)
        return
    }

    fmt.Println(result)

    j, _ := json.Marshal(user)
    fmt.Println(string(j))
}
$ go run dynamodb-get-item.go
{
  Item: {
    UserID: {
      N: "1"
    },
    Date: {
      S: "20171215"
    },
    Time: {
      S: "20171215120000"
    },
    Name: {
      S: "hoge"
    }
  }
}
{"user_id":1,"date":"20171215","time":"20171215120000","name":"hoge"}

Query

ユーザの名前変更履歴が見たい!とかあると思います。
そんな時はQueryで検索します。(GetItemsとかは無いようです)
http://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/#DynamoDB.Query

dynamodb-query.go
package main

import (
    "encoding/json"
    "fmt"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

type User struct {
    UserID int    `json:"user_id" dynamodbav:"UserID"`
    Date   string `json:"date" dynamodbav:"Date"`
    Time   string `json:"time" dynamodbav:"Time"`
    Name   string `json:"name" dynamodbav:"Name"`
}

func main() {
    svc := dynamodb.New(session.New(), aws.NewConfig().WithRegion("ap-northeast-1"))

    input := &dynamodb.QueryInput{
        TableName: aws.String("UserName"),
        ExpressionAttributeNames: map[string]*string{
            "#ID":   aws.String("UserID"), // alias付けれたりする
            "#Date": aws.String("Date"),   // 予約語はそのままだと怒られるので置換する
            "#Name": aws.String("Name"),   // 予約語は(
        },
        ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
            ":id": { // :を付けるのがセオリーのようです
                N: aws.String("1"),
            },
        },
        KeyConditionExpression: aws.String("#ID = :id"),         // 検索条件
        ProjectionExpression:   aws.String("#ID, #Date, #Name"), // 取得カラム
        ScanIndexForward:       aws.Bool(false),                 // ソートキーのソート順(指定しないと昇順)
        Limit:                  aws.Int64(100),                  // 取得件数の指定もできる
    }

    result, err := svc.Query(input)
    if err != nil {
        fmt.Println("[Query Error]", err)
        return
    }

    users := make([]*User, 0)
    if err := dynamodbattribute.UnmarshalListOfMaps(result.Items, &users); err != nil {
        fmt.Println("[Unmarshal Error]", err)
        return
    }

    fmt.Println(result)

    j, _ := json.Marshal(users)
    fmt.Println(string(j))
}


$ go run dynamodb-query.go
{
  Count: 2,
  Items: [{
      Date: {
        S: "20171216"
      },
      UserID: {
        N: "1"
      },
      Name: {
        S: "fuga"
      }
    },{
      Date: {
        S: "20171215"
      },
      UserID: {
        N: "1"
      },
      Name: {
        S: "hoge"
      }
    }],
  ScannedCount: 2
}
[{"user_id":1,"date":"20171216","time":"","name":"fuga"},{"user_id":1,"date":"20171215","time":"","name":"hoge"}]

日付だけ指定して取得したい時は?

じゃあ日付(ソートキー)だけ指定して取得したいな〜ってなるじゃないですか。

input := &dynamodb.QueryInput{
    TableName: aws.String("UserName"),
    ExpressionAttributeNames: map[string]*string{
        "#Date": aws.String("Date"),
    },
    ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
        ":date": {
            S: aws.String("20171215"),
        },
    },
    KeyConditionExpression: aws.String("#Date = :date"),
}
ValidationException: Query condition missed key schema element: UserID

となって、できません。

→そんな時はグローバルセカンダリインデックス

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html

GSIを使うと、パーティションキー・ソートキー以外での検索ができるようになるみたいです。

DateにGSIを張ってみます。
DynamoDB · AWS Console (1).png

これでGSIを使って検索できるようになりました。

input := &dynamodb.QueryInput{
    TableName: aws.String("UserName"),
    ExpressionAttributeNames: map[string]*string{
        "#Date": aws.String("Date"),
    },
    ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
        ":date": {
            S: aws.String("20171215"),
        },
    },
    KeyConditionExpression: aws.String("#Date = :date"),
    IndexName:              aws.String("Date-index"),
}
$ go run dynamodb-query.go
{
  Count: 1,
  Items: [{
      Date: {
        S: "20171215"
      },
      UserID: {
        N: "1"
      },
      Time: {
        S: "20171215120000"
      },
      Name: {
        S: "hoge"
      }
    }],
  ScannedCount: 1
}
[{"user_id":1,"date":"20171215","time":"20171215120000","name":"hoge"}]

ハピなる:cherry_blossom: