DynamoDBのScanIndexForwardで昇順/降順ソートでクエリ
Posted On 2024-12-22
DynamoDBでソートキーの昇順・降順を簡単に切り替えるために、ScanIndexForwardオプションを使う方法をTerraformとPythonのサンプルコードで検証してみました。 テーブル構造やインデックスを変えずに、ScanIndexForwardをTrue/Falseにするだけでソート順序が自在に切り替えられることが確認できました。
目次
はじめに
DynamoDBをクエリする際、ソートキーに対してScanIndexForward
のオプションで簡単に昇順降順を切り替えると知ったので確かめてみた
テーブル作成
Terraformでテーブルを作成する。
resource "aws_dynamodb_table" "example" {
name = "example-table"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PartitionKey"
range_key = "SortKey" # ソートキー
attribute {
name = "PartitionKey"
type = "S" # S: String
}
attribute {
name = "SortKey"
type = "S" # ISO8601形式のタイムスタンプとして保存
}
}
テーブル構造は以下の通りで、
PartitionKey
:パーティションキーで、具体的にはユーザーIDを追加する。ここではソートの例を示すために固定でデータを登録するSortKey
:昇順/降順ソートを確認するためのソートキー、任意のタイムスタンプを登録する
アイテムを追加
以下のコードで10個ほどアイテムを登録する
import boto3
from botocore.exceptions import ClientError
from datetime import datetime, timedelta, timezone
import time
# DynamoDBリソースの取得
session = boto3.Session(profile_name='hogehoge') # プロファイル名を指定
dynamodb = session.resource('dynamodb', region_name='ap-northeast-1') # リージョンを指定
# テーブル名を指定
table = dynamodb.Table('example-table')
def generate_item(user_id, index):
"""
アイテムを生成する関数
同じPartitionKeyを使用し、SortKeyをタイムスタンプに設定
"""
time.sleep(0.1)
return {
'PartitionKey': f'User#{user_id}',
'SortKey': (datetime.now(timezone.utc) - timedelta(minutes=index)).isoformat(), # タイムスタンプをソートキーに
'Data': {
'Attribute1': f'Value{index}',
'Attribute2': index,
'Attribute3': True
},
'CreatedAt': datetime.now(timezone.utc).isoformat()
}
def batch_write_items(items):
"""
バッチでアイテムを書き込む関数
"""
try:
with table.batch_writer() as batch:
for item in items:
batch.put_item(Item=item)
print(f"Successfully added {len(items)} items.")
except ClientError as e:
print(f"Failed to add items: {e.response['Error']['Message']}")
if __name__ == "__main__":
# 追加するアイテムのリストを生成
items_to_add = [generate_item(user_id=1000, index=i) for i in range(10)] # 同じIDを使用
# アイテムをバッチで書き込む
batch_write_items(items_to_add)
マネジメントコンソールから確認すると以下のように登録されている。ソートキーには現在時刻からインデックスぶんの分数を引いているので、SortKey
とCreatedAt
の順番は逆になっている。
ScanIndexForwardを変えてクエリしてみる
ソートキーに対する昇順・降順の切り替えは、table.query
のScanIndexForward
で切り替える。true
なら昇順、false
なら降順になる。
# query_items.py
import boto3
from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key
# DynamoDBリソースの取得
session = boto3.Session(profile_name='hogehoge') # プロファイル名を指定
dynamodb = session.resource('dynamodb', region_name='ap-northeast-1') # リージョンを指定
# テーブル名を指定
table = dynamodb.Table('example-table')
def query_items(partition_key, scan_index_forward=True, limit=10):
"""
パーティションキーに基づいてアイテムをクエリし、ソートキーの順序を指定する関数
"""
try:
response = table.query(
KeyConditionExpression=Key('PartitionKey').eq(partition_key),
ScanIndexForward=scan_index_forward, # 昇順=True, 降順=False
Limit=limit # 必要に応じて制限
)
items = response.get('Items', [])
order = '昇順' if scan_index_forward else '降順'
print(f"\nクエリ結果({order}):")
for item in items:
print(item)
except ClientError as e:
print(f"クエリに失敗しました: {e.response['Error']['Message']}")
if __name__ == "__main__":
# クエリするパーティションキーを指定
partition_key = "User#1000" # 例として追加したアイテムの一つを使用
# 昇順でクエリ
query_items(partition_key, scan_index_forward=True)
# 降順でクエリ
query_items(partition_key, scan_index_forward=False)
結果は以下の通り
クエリ結果(昇順):
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:30:03.407969+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('9'), 'Attribute1': 'Value9'}, 'CreatedAt': '2024-12-21T16:39:03.407969+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:31:03.298998+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('8'), 'Attribute1': 'Value8'}, 'CreatedAt': '2024-12-21T16:39:03.298998+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:32:03.192171+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('7'), 'Attribute1': 'Value7'}, 'CreatedAt': '2024-12-21T16:39:03.192171+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:33:03.081185+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('6'), 'Attribute1': 'Value6'}, 'CreatedAt': '2024-12-21T16:39:03.081185+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:34:02.971412+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('5'), 'Attribute1': 'Value5'}, 'CreatedAt': '2024-12-21T16:39:02.971412+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:35:02.863380+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('4'), 'Attribute1': 'Value4'}, 'CreatedAt': '2024-12-21T16:39:02.863380+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:36:02.754544+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('3'), 'Attribute1': 'Value3'}, 'CreatedAt': '2024-12-21T16:39:02.754544+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:37:02.646470+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('2'), 'Attribute1': 'Value2'}, 'CreatedAt': '2024-12-21T16:39:02.646470+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:38:02.540057+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('1'), 'Attribute1': 'Value1'}, 'CreatedAt': '2024-12-21T16:39:02.540057+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:39:02.431351+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('0'), 'Attribute1': 'Value0'}, 'CreatedAt': '2024-12-21T16:39:02.431351+00:00'}
クエリ結果(降順):
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:39:02.431351+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('0'), 'Attribute1': 'Value0'}, 'CreatedAt': '2024-12-21T16:39:02.431351+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:38:02.540057+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('1'), 'Attribute1': 'Value1'}, 'CreatedAt': '2024-12-21T16:39:02.540057+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:37:02.646470+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('2'), 'Attribute1': 'Value2'}, 'CreatedAt': '2024-12-21T16:39:02.646470+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:36:02.754544+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('3'), 'Attribute1': 'Value3'}, 'CreatedAt': '2024-12-21T16:39:02.754544+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:35:02.863380+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('4'), 'Attribute1': 'Value4'}, 'CreatedAt': '2024-12-21T16:39:02.863380+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:34:02.971412+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('5'), 'Attribute1': 'Value5'}, 'CreatedAt': '2024-12-21T16:39:02.971412+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:33:03.081185+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('6'), 'Attribute1': 'Value6'}, 'CreatedAt': '2024-12-21T16:39:03.081185+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:32:03.192171+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('7'), 'Attribute1': 'Value7'}, 'CreatedAt': '2024-12-21T16:39:03.192171+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:31:03.298998+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('8'), 'Attribute1': 'Value8'}, 'CreatedAt': '2024-12-21T16:39:03.298998+00:00'}
{'PartitionKey': 'User#1000', 'SortKey': '2024-12-21T16:30:03.407969+00:00', 'Data': {'Attribute3': True, 'Attribute2': Decimal('9'), 'Attribute1': 'Value9'}, 'CreatedAt': '2024-12-21T16:39:03.407969+00:00'}
SortKeyに対して最初が昇順で、後が降順で、確かに想定された結果になった。
まとめ
- クエリする際のオプション
ScanIndexForward
を変えてしまえば、追加のインデックスなし・テーブル構造を変えずに昇順/降順を切り替えられるので便利!
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー