miso_soup3 Blog

主に ASP.NET 関連について書いています。

SET and REMOVE in an UpdateExpression DynamoDB

DynamoDB の put-item にて、一部の属性の値のみを更新しつつ、とある属性を削除したかったため、動作を確認しました。

An update expression consists of one or more clauses. Each clause begins with a SET, REMOVE, ADD, or DELETE keyword. You can include any of these clauses in an update expression, in any order. However, each action keyword can appear only once.

ドキュメントにもある通り、put-item では、SET, REMOVE, ADD, DELETE のアクションを指定でき、それらを複数指定できます。これらのアクションは、put-item内で1度のみしか 使えないようです。

事前準備

テーブル TableName: Members, Key: id を作成します。

aws dynamodb create-table \
    --table-name Members \
    --attribute-definitions \
        AttributeName=id,AttributeType=S \
    --key-schema AttributeName=id,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST 

データを2件投入します。

  • id: "1001", name: "takada", group_id: "100", location: "Tokyo"
  • id: "1002", name: "ishikawa", group_id: "200", location: "Hokkaido"
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"1001\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"S\": \"Tokyo\"}}"
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"1002\"}, \"name\": {\"S\": \"ishikawa\"}, \"group_id\": {\"S\": \"200\"}, \"location\": {\"S\": \"Hokkaido\"}}"

SET のみ

まずは SET のみを使い、一部の属性のみ更新できるかを確認します。

id:1001 の location を、Tokyo から Chiba に変更します。location は、DynamoDB の予約語なので、--expression-attribute-names#locationを定義します。また、--return-values ALL_NEWで更新後の結果を返すように指定します。

結果を見ると、location 以外の値は変更されていないことを確認できます。

aws dynamodb update-item \
    --table-name Members \
    --key "{ \"id\": { \"S\":\"1001\" } }" \
    --expression-attribute-names "{ \"#location\": \"location\" }" \
    --update-expression "SET #location = :location_value" \
    --expression-attribute-values \
        " { \":location_value\": { \"S\": \"Chiba\" } }" \
    --return-values ALL_NEW
{
    "Attributes": {
        "location": {
            "S": "Chiba"
        },
        "id": {
            "S": "1001"
        },
        "name": {
            "S": "takada"
        },
        "group_id": {
            "S": "100"
        }
    }
}

SET と REMOVE

locationを変更しつつ、group_idを削除する put-item のオペレーションを実行してみます。ちなみに、group_idが削除されている状態のときに実行しても問題なく成功します。

update-expression はこのように指定します: --update-expression "SET #location = :location_value REMOVE group_id"REMOVEの前に,は不要です。

aws dynamodb update-item \
    --table-name Members \
    --key "{ \"id\": { \"S\":\"1001\" } }" \
    --expression-attribute-names "{ \"#location\": \"location\" }" \
    --update-expression "SET #location = :location_value REMOVE group_id" \
    --expression-attribute-values \
        " { \":location_value\": { \"S\": \"Kyoto\" } }" \
    --return-values ALL_NEW
{
    "Attributes": {
        "id": {
            "S": "1001"
        },
        "name": {
            "S": "takada"
        },
        "location": {
            "S": "Kyoto"
        }
    }
}
お試し

ここでお試しとして、同じ属性を SET と REMOVE で指定してみます。このように: --update-expression "SET #location = :location_value REMOVE #location"。実際に使うことはないと思われます。

結果は、以下のようにエラーが発生します。各アクションで同じ属性(document paths)は指定できないということですね。

aws dynamodb update-item \
    --table-name Members \
    --key "{ \"id\": { \"S\":\"1001\" } }" \
    --expression-attribute-names "{ \"#location\": \"location\" }" \
    --update-expression "SET #location = :location_value REMOVE #location" \
    --expression-attribute-values \
        " { \":location_value\": { \"S\": \"Kagawa\" } }" \
    --return-values ALL_NEW
An error occurred (ValidationException) when calling the UpdateItem operation: Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths; path one: [location], path two: [location]

さらにお試しとして、SET -> REMOVE -> SET を指定してみます。このように: --update-expression "SET #location = :location_value REMOVE group_id SET #name = :name_value"。ちなみにnameも DynamoDB の予約語です。こちらも実際に使うことはないと思われます。

SET が複数指定されているので、ドキュメントにも記載されているようにエラーが発生します。

aws dynamodb update-item \
    --table-name Members \
    --key "{ \"id\": { \"S\":\"1001\" } }" \
    --expression-attribute-names "{ \"#location\": \"location\", \"#name\": \"name\" }" \
    --update-expression "SET #location = :location_value REMOVE group_id SET #name = :name_value" \
    --expression-attribute-values \
        " { \":location_value\": { \"S\": \"Kagawa\" }, \":name_value\": { \"S\": \"Doraemon\" } }" \
    --return-values ALL_NEW
An error occurred (ValidationException) when calling the UpdateItem operation: Invalid UpdateExpression: The "SET" section can only be used once in an update expression;

DynamoDB の GSI のレンジキーの値を定義時とは違う型で put-item できないことを確認する

Summary

  • DynamoDB のテーブルに GSI を定義するときは、AttributeDefinitions でハッシュキーやレンジキーの名前と型を定義することになる
  • GSI を定義したあと、その型と違うデータ型としてput-itemはできない
  • 例えば、DynamoDB の GSI のレンジキーを文字列としたとき、NULL型("filed-name": {"NULL": true }など)として更新(put-item)はできない。
    • カラム自体を定義しなければ、更新可能

再現手順

DynamoDB で、テーブルを作成し、いくつかデータを登録したあと、GSIを追加して、再度同じようなデータを作成し、結果を確認する。

テーブルを作成する
aws dynamodb create-table \
    --table-name Members \
    --attribute-definitions \
        AttributeName=id,AttributeType=S \
    --key-schema AttributeName=id,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST 
{
    "TableDescription": {
        "AttributeDefinitions": [
            {
                "AttributeName": "id",
                "AttributeType": "S"
            }
        ],
        "TableName": "Members",
        "KeySchema": [
            {
                "AttributeName": "id",
                "KeyType": "HASH"
            }
        ],
        "TableStatus": "CREATING",
        "CreationDateTime": 1571288732.889,
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 0,
            "WriteCapacityUnits": 0
        },
        "TableSizeBytes": 0,
        "ItemCount": 0,
        "TableArn": "arn:aws:dynamodb:***:***:table/Members",
        "TableId": "692f8720-***",
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST"
        }
    }
}
データを作成する
# A: locationあり
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"a\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"S\": \"Tokyo\"}}"
# B: locationあり2
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"b\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"S\": \"Toyama\"}}"
# C: location null型
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"c\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"NULL\": true }}"
# D: location のカラム自体がない
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"d\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}}"

A ~ D のput-itemに成功する。

GSIを作成する
# group_id, location で GSI作成
aws dynamodb update-table \
    --table-name Members \
    --billing-mode PAY_PER_REQUEST \
    --attribute-definitions \
    "[{ \"AttributeName\": \"id\", \"AttributeType\": \"S\" }, { \"AttributeName\": \"group_id\", \"AttributeType\": \"S\" }, { \"AttributeName\": \"location\", \"AttributeType\": \"S\" }]" \
    --global-secondary-index-updates \
    "[{ \"Create\": { \"IndexName\": \"group_id-location\", \"KeySchema\": [ { \"AttributeName\": \"group_id\", \"KeyType\": \"HASH\" }, { \"AttributeName\": \"location\", \"KeyType\": \"RANGE\" } ], \"Projection\": { \"ProjectionType\": \"ALL\" } } }]"
作成したGSIでqueryする

インデックスが完了してからqueryする

# Query
aws dynamodb query \
    --table-name Members \
    --index-name group_id-location \
    --key-condition-expression 'group_id = :group_id' \
    --expression-attribute-value "{\":group_id\": {\"S\": \"100\"}}"

↓「C: location null」と「D: location のカラム自体がない」はヒットしない

{
    "Items": [
        {
            "location": {
                "S": "Tokyo"
            },
            "id": {
                "S": "a"
            },
            "name": {
                "S": "takada"
            },
            "group_id": {
                "S": "100"
            }
        },
        {
            "location": {
                "S": "Toyama"
            },
            "id": {
                "S": "b"
            },
            "name": {
                "S": "takada"
            },
            "group_id": {
                "S": "100"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}
再度同じパターンのデータを作成する
# E: locationあり
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"e\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"S\": \"Tokyo\"}}"
# F: locationあり2
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"f\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"S\": \"Toyama\"}}"

↓「G: location null」のパターンでは、エラーが発生し put-item はできない。Type mismatch for Index Key location Expected: S Actual: NULL とエラーが発生する。

# G: location null型
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"g\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}, \"location\": {\"NULL\": true }}"
An error occurred (ValidationException) when calling the PutItem operation: One or more parameter values were invalid: Type mismatch for Index Key location Expected: S Actual: NULL IndexName: group_id-location

↓「H: location のカラム自体がない」のパターンでは、put-itemが成功する

# H: location のカラム自体がない
aws dynamodb put-item \
    --table-name Members  \
        --item \
    "{\"id\": {\"S\": \"h\"}, \"name\": {\"S\": \"takada\"}, \"group_id\": {\"S\": \"100\"}}"
再度queryする
# Query
aws dynamodb query \
    --table-name Members \
    --index-name group_id-location \
    --key-condition-expression 'group_id = :group_id' \
    --expression-attribute-value "{\":group_id\": {\"S\": \"100\"}}"

↓locationの値が設定されたデータA,B,E,Fがヒットする

{
    "Items": [
        {
            "location": {
                "S": "Tokyo"
            },
            "id": {
                "S": "e"
            },
            "name": {
                "S": "takada"
            },
            "group_id": {
                "S": "100"
            }
        },
        {
            "location": {
                "S": "Tokyo"
            },
            "id": {
                "S": "a"
            },
            "name": {
                "S": "takada"
            },
            "group_id": {
                "S": "100"
            }
        },
        {
            "location": {
                "S": "Toyama"
            },
            "id": {
                "S": "f"
            },
            "name": {
                "S": "takada"
            },
            "group_id": {
                "S": "100"
            }
        },
        {
            "location": {
                "S": "Toyama"
            },
            "id": {
                "S": "b"
            },
            "name": {
                "S": "takada"
            },
            "group_id": {
                "S": "100"
            }
        }
    ],
    "Count": 4,
    "ScannedCount": 4,
    "ConsumedCapacity": null
}