DynamoDBは、パーティションキー(とソートキー)で項目を出し入れするAWSのデータベースで、キーを指定して取るのが query、テーブル全体を読むのが scan です。 操作は AWS CLI の aws dynamodb put-item や query、Python なら boto3 の client と resource から行えます。

この記事では、同じ操作を AWS CLI・boto3 client・boto3 resource の3通りで並べ、応答をそのまま載せます。{"S": "..."} という型記述子の読み方、キーの設計によって query に書ける条件と書けない条件、begins_with・between・並び順・GSI・ページング、そして予約語や Decimal で止まったときのエラー文と直し方まで、順に動かして確かめた内容です。

確認環境:Ubuntu 24.04 / AWS CLI 1.46.1 / Python 3.11.15 / boto3 1.43.103。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しています。掲載コマンドは接続先の指定を外してあり、実際の AWS でそのまま使える形です。

DynamoDB操作の早見表(CLI・client・resource)

やりたいこと AWS CLI boto3 client boto3 resource(Table)
1件入れる aws dynamodb put-item --item client.put_item(TableName=, Item=) table.put_item(Item=)
キーで1件取る get-item --key client.get_item(TableName=, Key=) table.get_item(Key=)
キーの条件で複数取る query --key-condition-expression client.query(KeyConditionExpression="...") table.query(KeyConditionExpression=Key(...).eq(...))
全体を読む scan client.scan(TableName=) table.scan()
一部の属性を書き換える update-item --update-expression client.update_item(...) table.update_item(...)
1件消す delete-item --key client.delete_item(...) table.delete_item(Key=)
まとめて入れる batch-write-item client.batch_write_item(...) with table.batch_writer() as batch:
値の書き方 {"S":"C001"} と型を付ける {"S": "C001"} と型を付ける "C001" と Python の値のまま
数値の受け取り {"N":"3"}(文字列) {"N": "3"}(文字列) Decimal("3")
用語 意味
項目(Item) 1件のデータ。リレーショナルデータベースの行に当たる
属性(Attribute) 項目の中の1つの値。列に当たるが、項目ごとに持つ属性は自由
パーティションキー 項目の置き場所を決めるキー(KeyType は HASH)。query では必ず「等しい」で指定
ソートキー 同じパーティションキーの中での並び順を決めるキー(KeyType は RANGE)。範囲や前方一致で絞れる
GSI グローバルセカンダリインデックス。別の属性をキーにして query できるようにする仕組み

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「DynamoDBの使い方|AWS CLIとboto3のquery・scan」

テーブルを作る(キー設計つき)

注文データを例に、パーティションキーを customer_id(顧客)、ソートキーを order_id(「日付#連番」の文字列)にします。さらに status で引けるよう GSI を1つ付けます。

aws dynamodb create-table --table-name Orders \
  --attribute-definitions \
      AttributeName=customer_id,AttributeType=S \
      AttributeName=order_id,AttributeType=S \
      AttributeName=status,AttributeType=S \
  --key-schema AttributeName=customer_id,KeyType=HASH AttributeName=order_id,KeyType=RANGE \
  --global-secondary-indexes '[{"IndexName":"status-index","KeySchema":[{"AttributeName":"status","KeyType":"HASH"},{"AttributeName":"order_id","KeyType":"RANGE"}],"Projection":{"ProjectionType":"ALL"}}]' \
  --billing-mode PAY_PER_REQUEST \
  --query 'TableDescription.[TableName,TableStatus]' --output text
aws dynamodb wait table-exists --table-name Orders
Orders  ACTIVE

--attribute-definitions に書くのはキーに使う属性だけです。それ以外の属性(品名や数量)は、項目を入れるときに自由に持たせられます。PAY_PER_REQUEST は、あらかじめ容量を決めないオンデマンドの指定です。

ソートキーを「2026-10-01#0001」のように日付から始まる文字列にしておくと、「10月分」「10月2日から月末まで」のような絞り込みが query だけで書けるようになります。この設計の効き目は、後の query の節で確かめます。

型記述子の一覧(S・N・BOOL・L・M)

CLI と boto3 client では、値に型を示す1〜4文字の記述子を付けます。resource は Python の値をそのまま渡せて、内部で変換されます。

記述子 型 CLI・client での書き方 resource に渡す Python の値 resource で受け取る値
S 文字列 {"S": "ノート"} "ノート" str
N 数値 {"N": "3"}(値は文字列で書く) 3 や Decimal("1.5") Decimal
BOOL 真偽値 {"BOOL": false} False bool
NULL 空 {"NULL": true} None None
L リスト {"L": [{"S": "a"}]} ["a"] list
M マップ {"M": {"city": {"S": "東京"}}} {"city": "東京"} dict
SS 文字列のセット {"SS": ["A", "B"]} {"A", "B"} set
NS 数値のセット {"NS": ["1", "2"]} {1, 2} set
B バイナリ {"B": ...} b"ab" Binary(.value で bytes)

N の値は "3" と文字列で書くのが最初につまずく点です。{"N":3} と数値のまま書くと、通信の前に Invalid type for parameter Item.qty.N, value: 3, type: <class 'int'>, valid types: <class 'str'> という引数エラーで止まります。

項目を入れる・取る(put-item と get-item)

AWS CLI

aws dynamodb put-item --table-name Orders --item '{
  "customer_id": {"S": "C001"},
  "order_id":    {"S": "2026-10-01#0001"},
  "name":        {"S": "ノート"},
  "status":      {"S": "NEW"},
  "qty":         {"N": "3"},
  "price":       {"N": "120.5"},
  "gift":        {"BOOL": false},
  "tags":        {"L": [{"S": "文具"}, {"S": "紙"}]},
  "address":     {"M": {"city": {"S": "東京"}}}
}'
aws dynamodb get-item --table-name Orders \
  --key '{"customer_id":{"S":"C001"},"order_id":{"S":"2026-10-01#0001"}}' \
  --projection-expression 'qty, tags'
{
    "Item": {
        "qty": {
            "N": "3"
        },
        "tags": {
            "L": [
                {
                    "S": "文具"
                },
                {
                    "S": "紙"
                }
            ]
        }
    }
}

put-item は成功すると何も表示しません。同じキーの項目が既にある場合は、項目全体が置き換わります。get-item はパーティションキーとソートキーの両方を指定します。--projection-expression は返す属性を絞る指定です。該当が無い場合はエラーではなく、何も表示されずに終了コード0で終わります。

続きの例のために、あと4件入れておきます。

put() { aws dynamodb put-item --table-name Orders --item \
  "{\"customer_id\":{\"S\":\"$1\"},\"order_id\":{\"S\":\"$2\"},\"name\":{\"S\":\"$3\"},\"status\":{\"S\":\"$4\"},\"qty\":{\"N\":\"$5\"}}"; }
put C001 '2026-10-02#0002' ペン SHIPPED 10
put C001 '2026-10-15#0003' 付箋 NEW 5
put C001 '2026-11-03#0004' ファイル DONE 2
put C002 '2026-10-05#0005' のり NEW 1
aws dynamodb scan --table-name Orders --select COUNT --query Count
5

boto3 client

client は CLI と同じ型記述子つきの形で渡し、同じ形で受け取ります。

import boto3
from decimal import Decimal

client = boto3.client("dynamodb")

client.put_item(TableName="Orders", Item={
    "customer_id": {"S": "C003"}, "order_id": {"S": "2026-10-20#0006"},
    "name": {"S": "はさみ"}, "status": {"S": "NEW"}, "qty": {"N": "2"},
})
r = client.get_item(TableName="Orders",
                    Key={"customer_id": {"S": "C003"}, "order_id": {"S": "2026-10-20#0006"}})
print(r["Item"])
{'customer_id': {'S': 'C003'}, 'order_id': {'S': '2026-10-20#0006'}, 'name': {'S': 'はさみ'}, 'status': {'S': 'NEW'}, 'qty': {'N': '2'}}

boto3 resource

resource の Table は Python の値のまま扱えます。

table = boto3.resource("dynamodb").Table("Orders")

table.put_item(Item={
    "customer_id": "C003", "order_id": "2026-10-21#0007",
    "name": "定規", "status": "NEW", "qty": 4, "price": Decimal("98.5"),
    "gift": True, "tags": ["文具"], "address": {"city": "大阪"}, "memo": None,
})
r = table.get_item(Key={"customer_id": "C003", "order_id": "2026-10-21#0007"})
print(r["Item"])
print("無いキー:", "Item" in table.get_item(Key={"customer_id": "C003", "order_id": "none"}))
{'customer_id': 'C003', 'order_id': '2026-10-21#0007', 'name': '定規', 'status': 'NEW', 'qty': Decimal('4'), 'price': Decimal('98.5'), 'gift': True, 'tags': ['文具'], 'address': {'city': '大阪'}, 'memo': None}
無いキー: False

数値は Decimal で返ります。該当が無いときは応答に Item キーが入らないので、r.get("Item") で受けると None で判定できます。

queryで取る:キーで書ける条件・書けない条件

条件の対応表

query の KeyConditionExpression に書けるのはキーの属性だけで、書ける演算も決まっています(公式開発者ガイド「Key condition expressions」)。

対象 書ける条件 CLI・client の式 resource の書き方
パーティションキー 等しい(必須) customer_id = :c Key("customer_id").eq("C001")
ソートキー 等しい order_id = :o Key("order_id").eq(...)
ソートキー 大小比較 order_id < :o(<=、>、>= も) .lt() .lte() .gt() .gte()
ソートキー 範囲(両端を含む) order_id BETWEEN :a AND :b .between(a, b)
ソートキー 前方一致 begins_with(order_id, :p) .begins_with(p)
書けない条件 代わりの方法
パーティションキーの範囲や前方一致 キーの設計を見直すか、GSI を作る
パーティションキーを省いてソートキーだけ GSI を作る。少量なら scan
ソートキーの部分一致(contains)や後方一致 FilterExpression で絞る、または検索したい値を先頭に置いた属性を持たせる
キー以外の属性(qty など)での絞り込み FilterExpression(読んだ後の絞り込み)か GSI
OR 条件 query を分けて結果を合わせる

CLI で query する

aws dynamodb query --table-name Orders \
  --key-condition-expression 'customer_id = :c AND begins_with(order_id, :p)' \
  --expression-attribute-values '{":c":{"S":"C001"},":p":{"S":"2026-10"}}' \
  --query '{Count:Count,ScannedCount:ScannedCount,ids:Items[].order_id.S}'
{
    "Count": 3,
    "ScannedCount": 3,
    "ids": [
        "2026-10-01#0001",
        "2026-10-02#0002",
        "2026-10-15#0003"
    ]
}

:c や :p は値の置き場所(プレースホルダー)で、実際の値は --expression-attribute-values に型記述子つきで渡します。--query は CLI 側で応答から必要な部分だけを取り出す指定で、ここでは件数と order_id だけを表示しています。Count は返った件数、ScannedCount は絞り込み前に読んだ件数です。

boto3 resource で query する

resource では Key を使って条件を組み立てます。

from boto3.dynamodb.conditions import Key, Attr

def ids(r):
    return [i["order_id"] for i in r["Items"]]

pk = Key("customer_id").eq("C001")
print("全件     ", ids(table.query(KeyConditionExpression=pk)))
print("等しい   ", ids(table.query(KeyConditionExpression=pk & Key("order_id").eq("2026-10-15#0003"))))
print("前方一致 ", ids(table.query(KeyConditionExpression=pk & Key("order_id").begins_with("2026-10"))))
print("範囲     ", ids(table.query(KeyConditionExpression=pk & Key("order_id").between("2026-10-02", "2026-10-31"))))
print("以上     ", ids(table.query(KeyConditionExpression=pk & Key("order_id").gte("2026-10-15"))))
print("より前   ", ids(table.query(KeyConditionExpression=pk & Key("order_id").lt("2026-10-15"))))
全件      ['2026-10-01#0001', '2026-10-02#0002', '2026-10-15#0003', '2026-11-03#0004']
等しい    ['2026-10-15#0003']
前方一致  ['2026-10-01#0001', '2026-10-02#0002', '2026-10-15#0003']
範囲      ['2026-10-02#0002', '2026-10-15#0003']
以上      ['2026-10-15#0003', '2026-11-03#0004']
より前    ['2026-10-01#0001', '2026-10-02#0002']

ソートキーを日付から始めたおかげで、月の前方一致や日付の範囲がそのまま書けています。文字列の大小は辞書順で比べるため、日付は「2026-10-02」のように桁をそろえた形にしておくのが前提です。

boto3 client で query する

client は CLI と同じ式の文字列を渡します。

r = client.query(
    TableName="Orders",
    KeyConditionExpression="customer_id = :c AND order_id BETWEEN :a AND :b",
    ExpressionAttributeValues={":c": {"S": "C001"}, ":a": {"S": "2026-10-02"}, ":b": {"S": "2026-10-31"}},
)
print(r["Count"], [i["order_id"]["S"] for i in r["Items"]])
2 ['2026-10-02#0002', '2026-10-15#0003']

書けない条件を書くとどうなるか

echo "--- ソートキーだけ"
aws dynamodb query --table-name Orders \
  --key-condition-expression 'order_id = :o' \
  --expression-attribute-values '{":o":{"S":"2026-10-01#0001"}}'
echo "--- パーティションキーを範囲で"
aws dynamodb query --table-name Orders \
  --key-condition-expression 'customer_id > :c' \
  --expression-attribute-values '{":c":{"S":"C000"}}'
--- ソートキーだけ

An error occurred (ValidationException) when calling the Query operation: Query condition missed key schema element: customer_id
--- パーティションキーを範囲で

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

いずれも ValidationException です。ソートキーに contains(order_id, :p) を書いた場合も Invalid KeyConditionExpression: Invalid function name; function: contains で止まります。エラーが出たら、上の対応表の「代わりの方法」に進みます。

並び順と件数(ScanIndexForward・Limit)

query の結果はソートキーの昇順で返ります。ScanIndexForward=False で降順になり、Limit=1 と組み合わせると「最新の1件」が取れます。

print("降順   ", ids(table.query(KeyConditionExpression=pk, ScanIndexForward=False)))
print("最新1件", ids(table.query(KeyConditionExpression=pk, ScanIndexForward=False, Limit=1)))
print("件数   ", table.query(KeyConditionExpression=pk, Select="COUNT")["Count"])
降順    ['2026-11-03#0004', '2026-10-15#0003', '2026-10-02#0002', '2026-10-01#0001']
最新1件 ['2026-11-03#0004']
件数    4

CLI では --no-scan-index-forward と --limit 1 を付けます。

キー以外で絞る(FilterExpression)

キー以外の属性で絞るときは FilterExpression を足します。resource では Attr を使います。

r = table.query(KeyConditionExpression=pk, FilterExpression=Attr("qty").gte(5))
print(ids(r), "Count", r["Count"], "ScannedCount", r["ScannedCount"])
['2026-10-02#0002', '2026-10-15#0003'] Count 2 ScannedCount 4

ScannedCount が4、Count が2です。FilterExpression は、キーの条件で読んだ後に適用される絞り込みなので、読む量(=消費する読み取り容量)は減りません。よく使う絞り込みは、ソートキーか GSI に載せるのが効率的です。

GSIで別のキーから引く

「status が NEW の注文」は、テーブルのキー(customer_id)では引けません。最初に作った status-index(パーティションキー status、ソートキー order_id)を IndexName で指定します。

r = table.query(IndexName="status-index", KeyConditionExpression=Key("status").eq("NEW"))
print([(i["customer_id"], i["order_id"]) for i in r["Items"]])

[('C001', '2026-10-01#0001'), ('C002', '2026-10-05#0005'), ('C001', '2026-10-15#0003'), ('C003', '2026-10-20#0006'), ('C003', '2026-10-21#0007')]

顧客をまたいで、status ごとに order_id の順で取れました。CLI では --index-name status-index を付けます。

GSI の性質を、公式開発者ガイドの記述と動かした結果からまとめます。

項目 内容
キーの重複 GSI のキーは一意でなくてよい(同じ status の項目が何件あってもよい)
読み取りの整合性 結果整合性のみ。ConsistentRead=True を付けると ValidationException
含める属性 Projection で KEYS_ONLY・INCLUDE・ALL から選ぶ
キー属性を持たない項目 インデックスに入らない(必要な項目だけを載せる使い方ができる)
from botocore.exceptions import ClientError
try:
    table.query(IndexName="status-index", KeyConditionExpression=Key("status").eq("NEW"), ConsistentRead=True)
except ClientError as e:
    print(e.response["Error"]["Code"], "|", e.response["Error"]["Message"])
ValidationException | Consistent reads are not supported on global secondary indexes

scanで全体を読む

scan はテーブル全体を端から読みます。条件は FilterExpression で付けます。

aws dynamodb scan --table-name Orders \
  --filter-expression 'qty >= :q' \
  --expression-attribute-values '{":q":{"N":"5"}}' \
  --query '{Count:Count,ScannedCount:ScannedCount,ids:Items[].order_id.S}' --output json
{
    "Count": 2,
    "ScannedCount": 7,
    "ids": [
        "2026-10-02#0002",
        "2026-10-15#0003"
    ]
}
r = table.scan(FilterExpression=Attr("status").eq("NEW") & Attr("qty").between(2, 5))
print(sorted(ids(r)), "Count", r["Count"], "ScannedCount", r["ScannedCount"])

['2026-10-01#0001', '2026-10-15#0003', '2026-10-20#0006', '2026-10-21#0007'] Count 4 ScannedCount 7

queryとscanの使い分け

観点 query scan
読む範囲 指定したパーティションキーの項目だけ テーブル(またはインデックス)全体
必要な指定 パーティションキーの「等しい」が必須 なし
並び順 ソートキーの昇順・降順 決まっていない
絞り込み キー条件+FilterExpression FilterExpression のみ
読み取り量 キー条件に合った分 全件分(絞り込んでも同じ)
向いている場面 画面表示や API など、ふだんの読み取り 件数の少ないテーブル、移行や集計の一括処理

公式開発者ガイドによると、scan の FilterExpression も読んだ後に適用され、読み取り容量の消費は減りません。まず query で取れるキー設計を考え、足りない引き方を GSI で補うのが基本の進め方です。

続きを取る(LastEvaluatedKeyのページング)

query と scan は、1回の応答が1MBまでという上限があり(公式開発者ガイド)、Limit を指定した場合もその件数で区切られます。続きがあるときは応答に LastEvaluatedKey が入るので、次の呼び出しの ExclusiveStartKey に渡します。

まず batch_writer で25件入れます。

with table.batch_writer() as batch:
    for i in range(1, 26):
        batch.put_item(Item={"customer_id": "C100", "order_id": f"2026-09-{i:02d}#{1000 + i}",
                             "status": "DONE", "qty": i})

kwargs = dict(KeyConditionExpression=Key("customer_id").eq("C100"), Limit=10)
items = []; page = 0
while True:
    r = table.query(**kwargs)
    page += 1; items += r["Items"]
    print(f"page {page}: Count={r['Count']} LastEvaluatedKey={r.get('LastEvaluatedKey')}")
    if "LastEvaluatedKey" not in r:
        break
    kwargs["ExclusiveStartKey"] = r["LastEvaluatedKey"]
print("total", len(items))
page 1: Count=10 LastEvaluatedKey={'customer_id': 'C100', 'order_id': '2026-09-10#1010'}
page 2: Count=10 LastEvaluatedKey={'customer_id': 'C100', 'order_id': '2026-09-20#1020'}
page 3: Count=5 LastEvaluatedKey=None
total 25

「LastEvaluatedKey が無くなるまで回す」が終了条件です。 件数が0かどうかでは判定しません。次の例のように、FilterExpression で絞ると0件や少数のページが返っても続きがあることがあるためです。

r = table.query(KeyConditionExpression=Key("customer_id").eq("C100"),
                FilterExpression=Attr("qty").gt(8), Limit=10)
print("Count", r["Count"], "ScannedCount", r["ScannedCount"], "続きあり:", "LastEvaluatedKey" in r)
Count 2 ScannedCount 10 続きあり: True

Limit=10 は「10件読む」の意味で、「絞り込んだ後に10件返す」ではありません。10件読んで、条件に合った2件だけが返りました。

client には paginator があり、ループを任せられます。

pg = client.get_paginator("query")
pages = pg.paginate(TableName="Orders", KeyConditionExpression="customer_id = :c",
                    ExpressionAttributeValues={":c": {"S": "C100"}},
                    PaginationConfig={"PageSize": 10})
print([p["Count"] for p in pages])
print("scan 全件:", sum(p["Count"] for p in client.get_paginator("scan").paginate(TableName="Orders")))
[10, 10, 5]
scan 全件: 32

CLI は既定で自動的に続きを取り、全件を返します。1ページだけ見たいときは --limit と --no-paginate を付けると、応答に LastEvaluatedKey が入ります。

更新する・消す(update-item と delete-item)

update は SET(値を入れる)、REMOVE(属性を消す)、ADD(数値を足す)を式で書きます。

aws dynamodb update-item --table-name Orders \
  --key '{"customer_id":{"S":"C001"},"order_id":{"S":"2026-10-01#0001"}}' \
  --update-expression 'SET #s = :s ADD qty :one' \
  --expression-attribute-names '{"#s":"status"}' \
  --expression-attribute-values '{":s":{"S":"SHIPPED"},":one":{"N":"1"}}' \
  --return-values UPDATED_NEW --query 'Attributes.[status.S,qty.N]' --output text
SHIPPED 4
K2 = {"customer_id": "C001", "order_id": "2026-10-02#0002"}

r = client.update_item(
    TableName="Orders",
    Key={"customer_id": {"S": "C001"}, "order_id": {"S": "2026-10-02#0002"}},
    UpdateExpression="SET #s = :s, qty = qty + :n",
    ExpressionAttributeNames={"#s": "status"},
    ExpressionAttributeValues={":s": {"S": "DONE"}, ":n": {"N": "1"}},
    ReturnValues="UPDATED_NEW",
)
print("client  :", r["Attributes"])

r = table.update_item(
    Key=K2,
    UpdateExpression="SET #s = :s, qty = qty + :n, memo = :m",
    ExpressionAttributeNames={"#s": "status"},
    ExpressionAttributeValues={":s": "SHIPPED", ":n": 1, ":m": "再送"},
    ReturnValues="ALL_NEW",
)
print("resource:", r["Attributes"])
client  : {'status': {'S': 'DONE'}, 'qty': {'N': '11'}}
resource: {'customer_id': 'C001', 'order_id': '2026-10-02#0002', 'name': 'ペン', 'status': 'SHIPPED', 'qty': Decimal('12'), 'memo': '再送'}

update_item は、キーに合う項目が無いと新しく作ります。作らせたくないときは条件を付けます(条件つき書き込みは次の節)。

r = table.update_item(Key={"customer_id": "C009", "order_id": "new"},
                      UpdateExpression="SET qty = :n", ExpressionAttributeValues={":n": 1},
                      ReturnValues="ALL_NEW")
print("作られた:", r["Attributes"])

r = table.delete_item(Key={"customer_id": "C009", "order_id": "new"}, ReturnValues="ALL_OLD")
print("消した  :", r.get("Attributes"))
r = table.delete_item(Key={"customer_id": "C009", "order_id": "new"}, ReturnValues="ALL_OLD")
print("もう一度:", r.get("Attributes"), r["ResponseMetadata"]["HTTPStatusCode"])
作られた: {'customer_id': 'C009', 'order_id': 'new', 'qty': Decimal('1')}
消した  : {'customer_id': 'C009', 'order_id': 'new', 'qty': Decimal('1')}
もう一度: None 200

無い項目を消してもエラーにはなりません。消えたかどうかを知りたいときは ReturnValues="ALL_OLD"(CLI は --return-values ALL_OLD)を付け、Attributes が返ったかで判断します。

よく出るエラーと直し方

予約語(name・status など)でValidationException

DynamoDB には式の中でそのまま書けない予約語があり、公式の予約語一覧には NAME、STATUS のほか DATE、YEAR、COUNT、SIZE、TYPE、USER、VALUE、KEY、DATA、COMMENT、TIMESTAMP、TTL、ORDER、ITEM、ITEMS などが載っています。属性名として使うこと自体はできますが、式に書くとエラーになります。

aws dynamodb query --table-name Orders \
  --key-condition-expression 'customer_id = :c' \
  --expression-attribute-values '{":c":{"S":"C001"}}' \
  --projection-expression 'order_id, name, status'
An error occurred (ValidationException) when calling the Query operation: ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name

直し方は、#n のような別名を式に書き、--expression-attribute-names で本当の名前を教えることです。

aws dynamodb query --table-name Orders \
  --key-condition-expression 'customer_id = :c' \
  --expression-attribute-values '{":c":{"S":"C001"}}' \
  --projection-expression 'order_id, #n, #s' \
  --expression-attribute-names '{"#n":"name","#s":"status"}' \
  --query 'Items[0].[order_id.S,name.S,status.S]' --output text
2026-10-01#0001 ノート SHIPPED

boto3 でも同じです。式を文字列で書く場合は別名が必要で、resource の Attr("name") を使う場合は別名が自動で付きます。

def show(label, f):
    try:
        f(); print(label, "-> OK")
    except ClientError as e:
        print(f"{label}\n  -> {e.response['Error']['Code']}: {e.response['Error']['Message']}")
    except Exception as e:
        print(f"{label}\n  -> {type(e).__name__}: {e}")

K1 = {"customer_id": "C001", "order_id": "2026-10-01#0001"}
show("直す前: SET status = :s",
     lambda: table.update_item(Key=K1, UpdateExpression="SET status = :s",
                               ExpressionAttributeValues={":s": "DONE"}))
show("直した後: SET #s = :s",
     lambda: table.update_item(Key=K1, UpdateExpression="SET #s = :s",
                               ExpressionAttributeNames={"#s": "status"},
                               ExpressionAttributeValues={":s": "DONE"}))
show("直した後: Attr('name').eq('ペン')",
     lambda: table.scan(FilterExpression=Attr("name").eq("ペン")))
直す前: SET status = :s
  -> ValidationException: Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: status
直した後: SET #s = :s -> OK
直した後: Attr('name').eq('ペン') -> OK
記号 役割 渡す先
#xxx 属性名の別名 ExpressionAttributeNames(CLI は --expression-attribute-names)
:xxx 値の置き場所 ExpressionAttributeValues(CLI は --expression-attribute-values)

別名や値は、定義したのに式で使っていないものがあってもエラーになります(Value provided in ExpressionAttributeValues unused in expressions: keys: {:z})。式を書き換えたあとは、対応する定義も一緒に消します。

キーが足りない・型が違う

show("put_item でソートキーなし",
     lambda: table.put_item(Item={"customer_id": "C001", "name": "x"}))
show("キーの型が違う(order_id に数値)",
     lambda: table.put_item(Item={"customer_id": "C001", "order_id": 1}))
show("キーの属性を update で書き換え",
     lambda: table.update_item(Key=K1, UpdateExpression="SET order_id = :q",
                               ExpressionAttributeValues={":q": "z"}))
put_item でソートキーなし
  -> ValidationException: One or more parameter values were invalid: Missing the key order_id in the item
キーの型が違う(order_id に数値)
  -> ValidationException: One or more parameter values were invalid: Type mismatch for key order_id expected: S actual: N
キーの属性を update で書き換え
  -> ValidationException: One or more parameter values were invalid: Cannot update attribute order_id. This attribute is part of the key
エラーの内容 原因 直し方
Missing the key order_id in the item put_item にソートキーが無い パーティションキーとソートキーを両方入れる
Type mismatch for key キーの型がテーブル定義と違う 定義(S・N)に合わせる。数字の ID も S なら文字列で
Cannot update attribute ... part of the key キーを update で変えようとした 新しいキーで put し、元の項目を delete する
Invalid type for parameter Key.customer_id ... valid types: dict client に Python の値をそのまま渡した {"S": "C001"} の形にするか、resource を使う
ResourceNotFoundException(Requested resource not found) テーブル名かリージョンが違う aws dynamodb list-tables で名前とリージョンを確認

get_item でキーの一部だけを渡した場合も ValidationException になります。キーは常に全部そろえて渡す、と覚えておくと迷いません。

Decimalの変換エラー

resource は小数を float では受け付けず、Decimal を求めます。

import json

show("直す前: float を渡す",
     lambda: table.put_item(Item={"customer_id": "C003", "order_id": "x", "price": 98.5}))

item = json.loads('{"customer_id": "C003", "order_id": "2026-10-22#0008", "price": 98.5, "qty": 1}',
                  parse_float=Decimal)
show("直した後: parse_float=Decimal で読み込む", lambda: table.put_item(Item=item))
print(item)
直す前: float を渡す
  -> TypeError: Float types are not supported. Use Decimal types instead.
直した後: parse_float=Decimal で読み込む -> OK
{'customer_id': 'C003', 'order_id': '2026-10-22#0008', 'price': Decimal('98.5'), 'qty': 1}

JSON から読み込むデータは parse_float=Decimal を付けておくと、まとめて Decimal になります。個別に作るときは Decimal("98.5") と文字列から作ります(Decimal(98.5) と float から作ると、誤差を含んだ長い桁になることがあります)。

逆に、取り出した項目を JSON にするときは Decimal が変換できずに止まります。

it = table.get_item(Key=K1)["Item"]
try:
    json.dumps(it)
except TypeError as e:
    print("直す前:", e)

def dec(o):
    if isinstance(o, Decimal):
        return int(o) if o == o.to_integral_value() else float(o)
    raise TypeError

print("直した後:", json.dumps(it, default=dec, ensure_ascii=False))
直す前: Object of type Decimal is not JSON serializable
直した後: {"customer_id": "C001", "order_id": "2026-10-01#0001", "name": "ノート", "status": "DONE", "qty": 4, "price": 120.5, "gift": false, "tags": ["文具", "紙"], "address": {"city": "東京"}}

ConditionalCheckFailedException(条件つき書き込み)

put_item は同じキーがあると黙って上書きします。「無いときだけ作る」には ConditionExpression を付け、条件に合わなかったときの例外を受けます。

aws dynamodb put-item --table-name Orders \
  --item '{"customer_id":{"S":"C001"},"order_id":{"S":"2026-10-01#0001"}}' \
  --condition-expression 'attribute_not_exists(customer_id)'
An error occurred (ConditionalCheckFailedException) when calling the PutItem operation: The conditional request failed
def create_order(item):
    try:
        table.put_item(Item=item, ConditionExpression="attribute_not_exists(customer_id)")
        return "created"
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
            return "already exists"
        raise

print(create_order({"customer_id": "C200", "order_id": "2026-10-03#2001", "qty": 1}))
print(create_order({"customer_id": "C200", "order_id": "2026-10-03#2001", "qty": 99}))
print(table.get_item(Key={"customer_id": "C200", "order_id": "2026-10-03#2001"})["Item"])
created
already exists
{'customer_id': 'C200', 'order_id': '2026-10-03#2001', 'qty': Decimal('1')}

2回目は書き込まれず、qty は1のままです。これはエラーというより「条件に合わなかった」という知らせなので、握りつぶさずに戻り値で呼び出し元へ伝える形にしています。

update_item にも同じ ConditionExpression を付けられます。#s = :old を条件にすれば「status が NEW のときだけ SHIPPED にする」という状態の進め方が書け、attribute_exists(customer_id) を条件にすれば、update_item が新しい項目を作ってしまう動きも止められます。どちらも条件に合わないときは ConditionalCheckFailedException が届きます。

clientとresourceの違い

観点 boto3 client boto3 resource(Table)
値の形 {"S": "C001"} と型記述子つき Python の値のまま
数値 {"N": "3"}(文字列) Decimal
条件の書き方 式の文字列+プレースホルダー Key()・Attr()、または式の文字列
予約語 #別名 が必要 Key()・Attr() なら自動。文字列の式では必要
ページング paginator がある LastEvaluatedKey を自分で回す
まとめて書く batch_write_item のリクエストを自分で組み立てる batch_writer に put_item していくだけ
CLI との対応 引数も応答もほぼ同じ形 形が異なる
例外 ClientError ClientError(同じ)

CLI で試したコマンドをそのままコードに移すなら client、Python の値で手短に書きたいなら resource が扱いやすいです。boto3 の公式ガイドには、resource インターフェイスに新機能を追加する予定はなく、新しい機能は client から使えるという注記があります。新しい機能を使う箇所は client、項目の出し入れは resource、と併用する書き方もできます。

よくある質問

Q. query と scan はどちらを使えばよいですか? A. パーティションキーが分かっているなら query です。読む範囲がそのキーの項目だけに限られます。scan はテーブル全体を読むので、件数の少ないテーブルや一括処理に向いています。

Q. put_item と update_item はどう使い分けますか? A. put_item は項目全体を置き換え、update_item は指定した属性だけを書き換えます。一部の値だけを変えたいときや、数量を1増やすといった計算をしたいときは update_item が向いています。

Q. 結果の件数が思ったより少ないのはなぜですか? A. 応答に LastEvaluatedKey が入っていないかを見てください。入っていれば続きがあります。boto3 では LastEvaluatedKey が無くなるまで回すか、client の paginator を使います。

まとめ

  • query はパーティションキーの「等しい」が必須。 ソートキーには等号・大小・BETWEEN・begins_with が書ける
  • CLI と client は {"S": "..."} の型記述子つき、resource は Python の値のまま。 N の値は文字列で書く
  • ソートキーを日付から始まる文字列にすると、月や期間の絞り込みが query で書ける
  • キー以外で引きたいときは GSI。 FilterExpression は読んだ後の絞り込みで、読む量は減らない
  • 続きは LastEvaluatedKey を ExclusiveStartKey に渡す。 無くなるまで回すのが終了条件
  • name・status などの予約語は #別名 と ExpressionAttributeNames で書く
  • 小数は Decimal で渡し、上書きを避けたい書き込みは ConditionExpression を付けて ConditionalCheckFailedException を受ける

参考・出典

  • Amazon DynamoDB 開発者ガイド「Key condition expressions for the Query operation」(キー条件に書ける演算、1MB の上限)
  • Amazon DynamoDB 開発者ガイド「Scanning tables」(FilterExpression の適用順、Limit、LastEvaluatedKey)
  • Amazon DynamoDB 開発者ガイド「Reserved words」(予約語の一覧、式の属性名による回避)
  • Amazon DynamoDB 開発者ガイド「Using Global Secondary Indexes」(整合性、Projection、キーの重複)
  • Boto3 ドキュメント「Resources」(resource への新機能追加についての注記)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「DynamoDBの使い方|AWS CLIとboto3のquery・scan」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?