使用クライアント: <https://github.com/guregu/dynamo>

このテーブルから、Key1を指定、Key2を複数指定したもので取得したい為、IN句を使用して値を取得する方法はありますか？

key1(HASH KEY) | key2 (SORT KEY) | val  
---|---|---  
A | 1 | val1  
A | 2 | val2  
B | 1 | val3  
  
SQLであれば、以下のイメージになります。

```

    select key1, key2, val1 from test where key1='A'and key2 in(1, 2)
    
```

クライアントは資料を読む限り、プレースホルダーに、任意の値を指定する機能しかありません。  
HashKeyだけを指定して、取得後にフィルターしてもいいのですが、  
可能であれば取得時に除外したいため、IN句の使用を検討しています。

クライアントとして書きたいイメージとしては、配列などからIN句に値が指定したいだけなので、以下のようなイメージになります。

```

    arr = []int{1,2}
    table := DB.Table("test")
    err = table.Get("key1", "A").Filter("'key2' IN (?)", arr).All(&results)
    
```

この場合、エラーにはなりませんが、値も取得できません。  
※GetとRangeでHashKeyとSortKeyを指定した場合、値の取得はできます。

例えば、以下の様に文字列として条件を指定した場合も、値を取得することはできません。おそらく2重にクォート処理されているのでしょう。

```

    table := DB.Table("test")
    err = table.Get("key1", "A").Filter("'key2' IN (?)", "'1','2'").All(&results)
    
```

<https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html>

IN句を使用してデータを取得できるかどうかの確認のため、CLIでも確認しました。

```

    aws --endpoint=http://localstack:4566 dynamodb query \
    --table-name test \
    --key-condition-expression 'key1= :key1' \
    --filter-expression 'key2 in (:key2)' \
    --expression-attribute-values '{ ":key1": { "S": "A" }, ":key2": { "N": "1" } }'
    
```

この場合、1件のデータが取得できます。

```

    aws --endpoint=http://localstack:4566 dynamodb query \
    --table-name test \
    --key-condition-expression 'key1= :key1' \
    --filter-expression 'key2 in (:key2, :key3)' \
    --expression-attribute-values '{ ":key1": { "S": "A" }, ":key2": { "N": "1" },":key3": { "N": "2" } }'
    
```

key2の条件を以下のようにすると、特に値を展開する機能がないのか、値が取得できません。

```

    '{"key2": {"L":[{"N": "1"}, {"N": "2"}] }}'
    
```

この場合も、文字列として扱われるためか、値が取得できません。

```

    '{"key2": {"S":"'1','2'"}}'
    
```

複数の任意の値をフィルターのIN条件に追加するためにはどのような方法がありますか？
CC BY-SA 4.0
2023-01-25T06:45:25.397
93557
2023-01-29T02:21:48.010
2023-01-26T02:46:54.643
19110
56755
question
go amazon-dynamodb
dynamodbクエリでINを使用する方法はありますか？
key2がソートキーなのでFilterが使えません。

>
> フィルタ式には、パーティションキーまたはソートキーの属性を含めることはできません。フィルタ式ではなく、キー条件式でこれらの属性を指定する必要があります。[[ソース]](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression)

BatchGetItemで取得できます。

```

    err := table.Batch("key1", "key2").
        Get(dynamo.Keys{"A", 1}, dynamo.Keys{"A", 2}).
        All(&results)
    
```
CC BY-SA 4.0
2023-01-28T15:24:36.073
93595
2023-01-28T15:24:36.073
4591
93557
answer
質問文の状況下では、`in` 演算子を使わずに BatchGetItem を使う方がオススメです。順番に説明します。

## プライマリーキーには `in` 演算子を使えない

まず、プライマリーキーに対しては
[FilterExpression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression)
を利用することができません。これは[複合プライマリーキー (composite primary
key)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey)
の場合も同じです。今回の例だと `key2` はソートキーであるため、FilterExpression で使うことができません。

また、KeyConditionExpression
で使える演算子は[限られており](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions)、特に
`in` 演算子を利用することができません。

したがって、`key2` に対して直接 `in` 演算子を使うことはできません。もし使おうとすると `Filter Expression can only
contain non-primary key attributes` というエラーが出ます。

```

    % aws dynamodb query \
        --table-name test \
        --key-condition-expression 'key1 = :v1' \
        --filter-expression 'key2 in (:v2, :v3)' \
        --expression-attribute-values '{":v1": {"S": "A"}, ":v2": {"N": "1"}, ":v3": {"N": "2"}}'
    
    An error occurred (ValidationException) when calling the Query operation: Filter Expression can only contain non-primary key attributes: Primary key attribute: key2
    
```

（※この部分の挙動が質問者さんの環境での挙動と異なるため、もしかすると質問者さんのお手元の DynamoDB
テーブルは何かしら説明と設定が食い違っているか、LocalStack を利用されているようなので挙動差があるのかもしれません。）

## セカンダリインデックスを使う方法

抜け道として、[セカンダリインデックス](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html)を使う方法があります。つまり、複合プライマリーキーを構成するパーティションキーとソートキー（別名：ハッシュ属性と範囲属性）のうちパーティションキーのみをプライマリキーとするセカンダリインデックスを作ってクエリ時にそちらを使うようにすれば、FilterExpression
で `key2` が利用できるようになります。

たとえば質問文のテーブル `test` に対して `key1-only-index` という名前で `key`
のみをプライマリキーとするセカンダリインデックスを作ってクエリした例が以下です。

```

    % aws dynamodb query \
        --table-name test \
        --index-name key1-only-index \
        --key-condition-expression 'key1 = :v1' \
        --filter-expression 'key2 in (:v2, :v3)' \
        --expression-attribute-values '{":v1": {"S": "A"}, ":v2": {"N": "1"}, ":v3": {"N": "2"}}'                                                             
    {
        "Items": [
            {
                "val": {
                    "S": "val2"
                },
                "key1": {
                    "S": "A"
                },
                "key2": {
                    "N": "2"
                }
            },
            {
                "val": {
                    "S": "val1"
                },
                "key1": {
                    "S": "A"
                },
                "key2": {
                    "N": "1"
                }
            }
        ],
        "Count": 2,
        "ScannedCount": 2,
        "ConsumedCapacity": null
    }
    
```

ただし、FilterExpression では **フィルターが適用される前のデータに対して** 1 MB のデータサイズ上限があります:
<https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions>

> A `Query` operation can retrieve a maximum of 1 MB of data. This limit
> applies before the filter expression is evaluated.

もともとソートキーであった `key2` をソートキーとして利用せずにフィルターしているため、DynamoDB
内でのデータ処理に無駄が生じる場合が多そうです。このときデータ量制限に引っかかる可能性も普段よりかは高くなっているでしょう。

## BatchGetItem を使う方法

ここまでの事情により、`key2` の候補の数が少ないのであれば、`in` 演算子を使うのではなく
[BatchGetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)
を使う方がオススメです。つまり、複数のソートキーをひとつずつ指定するやり方です。BatchGetItem
であればソートキーをきちんとソートキーとして利用できるため、テーブルの設計を上手くできていれば計算効率も高くなるでしょう。

具体的な実行例は以下です。

```

    % aws dynamodb batch-get-item --request-items '{"test": {"Keys": [{"key1": {"S": "A"}, "key2": {"N": "1"}}, {"key1": {"S": "A"}, "key2": {"N": "2"}}]}}'
    {
        "Responses": {
            "test": [
                {
                    "val": {
                        "S": "val1"
                    },
                    "key1": {
                        "S": "A"
                    },
                    "key2": {
                        "N": "1"
                    }
                },
                {
                    "val": {
                        "S": "val2"
                    },
                    "key1": {
                        "S": "A"
                    },
                    "key2": {
                        "N": "2"
                    }
                }
            ]
        },
        "UnprocessedKeys": {}
    }
    
```

今回クライアントとして利用されている <https://github.com/guregu/dynamo> にも
<https://pkg.go.dev/github.com/guregu/dynamo#Batch.Get> という形で BatchGetItem
の実装があるので、これを利用できそうです。
CC BY-SA 4.0
2023-01-28T15:26:48.563
93596
2023-01-29T02:21:48.010
2023-01-29T02:21:48.010
19110
19110
93557
answer