DynamoDBの scan() でフィルターを使って取得したデータが0件でも、続きのデータが存在するか確認してみた

DynamoDBのscan()やquery()で続きのデータがあるか判断するときは、取得したデータ件数ではなく、LastEvaluatedKeyで判断しましょう。
2021.08.05

DynamoDBテーブルでscan()query()を使うとき、フィルタを使うことがあります。 そこでふと気になりました。フィルタ適応後のデータが0件でも、続きはあるよね?と。

DynamoDB概要

最初にまとめ

DynamoDBでscan()やquery()を使うとき、「続きのデータがある?(最後のデータ?)」の判断は、LastEvaluatedKeyを参照しましょう。データ件数で判断しないように注意です。

DynamoDBテーブルを準備する

以前に作成したDynamoDBテーブルを流用します。

DynamoDBテーブルの様子

scan()で調べてみる

1回のscan()で最大1MBのデータを取得できますが、実際に用意するのは大変なので、Limitを使って取得するデータ量を減らします。

まずは、Limit無しで試してみる

Limit無しでtitleに「買う」が含まれているデータ取得できるか試してみます。

get_no_limit.py

import boto3
from boto3.dynamodb.conditions import Attr

def main():
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('todo-tag-sample-table')

    options = {
        'FilterExpression': Attr('title').contains('買う'),
    }
    res = table.scan(**options)

    if len(res.get('Items', [])) == 0:
        print('data is nothing.')
    else:
        print('data is exist.')
        data = res.get('Items', [])
        dump(data)

    if 'LastEvaluatedKey' not in res:
        print('LastEvaluatedKey is nothing.')
    else:
        print('LastEvaluatedKey is exist.')
        print(res['LastEvaluatedKey'])

def dump(items):
    for item in items:
        print(item)


if __name__ == '__main__':
    main()

タイトルに買うが含まれているデータを取得できました。また、1回のscan()で全データを取得できています(続きは無い)。

$ python get_no_limit.py

data is exist.
{'todoId': 't0001', 'tags': {'Amazon', '買い物', '単三'}, 'userId': 'u0001', 'title': '電池を買う'}
{'todoId': 't0003', 'userId': 'u0001', 'tags': {'買い物', 'スーパー'}, 'title': 'ちくわを買う'}
LastEvaluatedKey is nothing.

Limitをつけて、取得したデータが0件でも、続きがあるか試してみる

Limitを指定して、続きのデータがあるか試してみます。

get_no_limit.py

import boto3
from boto3.dynamodb.conditions import Attr

def main():
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('todo-tag-sample-table')

    options = {
        'FilterExpression': Attr('title').contains('買う'),
        'Limit': 1,
    }
    res = table.scan(**options)

    if len(res.get('Items', [])) == 0:
        print('data is nothing.')
    else:
        print('data is exist.')
        data = res.get('Items', [])
        dump(data)

    if 'LastEvaluatedKey' not in res:
        print('LastEvaluatedKey is nothing.')
    else:
        print('LastEvaluatedKey is exist.')
        print(res['LastEvaluatedKey'])


def dump(items):
    for item in items:
        print(item)


if __name__ == '__main__':
    main()

取得したデータは0件でしたが、LastEvaluatedKeyがあるので、続きのデータが存在することがわかります。

$ python get_no_limit.py

data is nothing.
LastEvaluatedKey is exist.
{'todoId': 't0005', 'userId': 'u1111'}

おまけ:Limitありで全データを取得する

最後のデータでない場合は、LastEvaluatedKeyが渡されるので、ループすると全データを取得できます。

get_limit_all_data.py

import boto3
from boto3.dynamodb.conditions import Attr

def main():
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('todo-tag-sample-table')

    options = {
        'FilterExpression': Attr('title').contains('買う'),
        'Limit': 1,
    }

    data = []
    while True:
        res = table.scan(**options)
        data += res.get('Items', [])

        if len(res.get('Items', [])) == 0:
            print('data is nothing.')
        else:
            print('data is exist.')
            data = res.get('Items', [])
            dump(data)

        if 'LastEvaluatedKey' not in res:
            print('LastEvaluatedKey is nothing.')
        else:
            print('LastEvaluatedKey is exist.')
            print(res['LastEvaluatedKey'])

        # 続きのデータが無ければ、取得ループを抜ける
        if 'LastEvaluatedKey' not in res:
            break
        options['ExclusiveStartKey'] = res['LastEvaluatedKey']



def dump(items):
    for item in items:
        print(item)


if __name__ == '__main__':
    main()

ループをすることで、全データの取得ができました。

$ python get_limit_all_data.py

data is nothing.
LastEvaluatedKey is exist.
{'todoId': 't0005', 'userId': 'u1111'}
data is nothing.
LastEvaluatedKey is exist.
{'todoId': 't0006', 'userId': 'u1111'}
data is exist.
{'todoId': 't0001', 'tags': {'Amazon', '単三', '買い物'}, 'userId': 'u0001', 'title': '電池を買う'}
LastEvaluatedKey is exist.
{'todoId': 't0001', 'userId': 'u0001'}
data is nothing.
LastEvaluatedKey is exist.
{'todoId': 't0002', 'userId': 'u0001'}
data is exist.
{'todoId': 't0003', 'userId': 'u0001', 'tags': {'スーパー', '買い物'}, 'title': 'ちくわを買う'}
LastEvaluatedKey is exist.
{'todoId': 't0003', 'userId': 'u0001'}
data is nothing.
LastEvaluatedKey is exist.
{'todoId': 't0004', 'userId': 'u0001'}
data is nothing.
LastEvaluatedKey is nothing.

参考