Iyarr
@Iyarr

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

DynamoDBのQueryCommandオブジェクトの作成エラー

解決したいこと

DynamoDBのソートキーとハッシュキーのあるテーブルに対して、ソートキーの条件のみ指定してデータを取得したい

取得したい属性

  • userName
  • partition

ハッシュキー、ソートキーをそれぞれ割り当てる
partitionのみを条件式として指定したい

該当するソースコード


    // テーブル作成時のコードの一部
const command = new CreateTableCommand({
      TableName: 'Shifts',
      AttributeDefinitions: [
        {
          AttributeName: 'partition',
          AttributeType: 'S',
        },
        {
          AttributeName: 'userName',
          AttributeType: 'S',
        },
      ],
      KeySchema: [
        {
          AttributeName: 'partition',
          KeyType: 'HASH',
        },
        {
          AttributeName: 'userName',
          KeyType: 'RANGE',
        },
      ],
      BillingMode: 'PROVISIONED',
      ProvisionedThroughput: {
        ReadCapacityUnits: 5,
        WriteCapacityUnits: 5,
      },
// コード終わり

DownloadShiftsBetweenPartitions(
    startpartition: string,
    endpartition: string,
  ) {
    const command = new QueryCommand({
      TableName: 'Shifts',
      KeyConditionExpression: '#hashKey BETWEEN :startValue AND :endValue',
      ExpressionAttributeNames: {
        '#hashKey': 'partition',
      },
      ExpressionAttributeValues: {
        ':startValue': { S: startpartition },
        ':endValue': { S: endpartition },
      },
    });

    //~~~~~~~~~~~~~~

  }

発生している問題・エラー

"Invalid KeyConditionExpression: Incorrect operand type for operator or function; operator or function: BETWEEN, operand type: M"

自分で試したこと

Query key condition not supported

指定方法が違うといわれました

  • そもそも複数のデータを取得すればいいのでIN演算子をためした
    • 無効な演算子だとエラーが出た
  • ハッシュキー、ソートキーをそれぞれ入れ替えた
    • BETWEENの指定はできるがハッシュキーを指定しなくてはいけなくなった
0

2Answer

こんにちは、ダイナモをラムダで記述するの難しいですよね。
ExpressionAttributeValues: {
':startValue': { S: startpartition },
':endValue': { S: endpartition },
},

これを

ExpressionAttributeValues: {
':startValue': startpartition ,
':endValue': endpartition,
},

これに変更してもダメですか?

0Like

Comments

  1. @Iyarr

    Questioner

    返信ありがとうございます。やってみまして問題の一つは解消されたようなのですが未だに同じエラーが発生します。
    BETWEENを使用する属性をソートキーにしてみたりもしてますがうまくいかないです。

  2. こちらに下記の内容が書かれていました。

    さて、ここで DynamoDB への検索可能な仕様をおさらいします。

    パーティションキーおよびソートキーにしか検索条件をかけられません。属性に対して検索をかける> ことはできません。
    パーティションキーは必ず検索条件に指定する必要があります。ソートキーのみを検索条件にするこ> とはできません。
    パーティションキーには、完全一致条件のみ指定できます。
    この例では、例えば「Bから始まる」というような部分一致条件は指定できません。
    一方、ソートキーには「1から始まる」というような部分一致条件が指定できます。それでも数える> > ほどのパターンのみですが。もちろん完全一致も指定できます。
    指定した条件にマッチした行のデータを1つまたは複数返してくれる、というのが検索でできること> > です。

    どうやらこの内容的にパーティションキーをbetweenするのは仕様的に不可能(パーティションキーには、完全一致条件のみ指定)で、ソートキーのみでbetweenすること(ソートキーのみの条件式)も不可能なようです。

残念ながらパーティションキーを指定せずに、ソートキーを元に検索をかけることは不可能です。
また単純に属性名「partition」をパーティションキーにしたグローバルセカンダリーインデックスを作成しても、パーティションキーにBETWEENなどの検索条件を付与することはできません。そのため面倒ではありますが以下の方法が可能かと思います

  1. テーブルに属性を一つ追加する

    • userName(パーティションキー)
    • partition(ソートキー)
    • constantValue(すべてのデータに同じ値を使用する。例えば0やnull、"Constant"など何でもOKです)
  2. グローバルセカンダリーインデックスを作成する

    • userName
    • partition(ソートキー)
    • constantValue(パーティションキー)

この設定をすることで以下のようなソースを作り、意図したクエリーが可能かと思います

  const command = new QueryCommand({
    TableName: 'Shifts',
    IndexName: 'ご自身で作られたインデックス名'
    // constantValueキーの値はすべて「Constant」にした想定
    KeyConditionExpression: '#ConstantKey = :constantValue AND #hashKey BETWEEN :startValue AND :endValue',
    ExpressionAttributeNames: {
      '#ConstantKey': 'constantValue'
      '#hashKey': 'partition',
    },
    ExpressionAttributeValues: {
         ':startValue': startpartition ,
         ':endValue': endpartition,
         ':constantValue': 'Constant',
    }
  })

以下蛇足になりますが、AWS-SDKにはDynamoDBを操作するのに便利なツールがあるのでこの機会にぜひご活用ください

  • DocumentClient
    DynamoDBからデータを取り出す・書き込むときにDynamoDBのJSON形式を意識する必要がないように裏でJavascriptの属性に置換してくれる
    例) Key: {S:"String"} → Key:"String"

  • Paginator
    DynamoDBには検索結果が一定数変えると再度続きのデータを取得するためにリクエストしなければなりませんが、それを自動でやってくれる

    import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
    import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'
    import { paginateQuery } from '@aws-sdk/lib-dynamodb'

    const dynamoDBClient = new DynamoDBClient()
    const docClient = DynamoDBDocument.from(dynamoDBClient, {
      // DynamoDBに書き込む際に、空文字をNullに変換、Undefinedの値を削除
      marshallOptions: { convertEmptyValues: true, removeUndefinedValues: true },
      unmarshallOptions: {}
    })

    const params = {
       TableName: 'Shifts',
       IndexName: 'ご自身で作られたインデックス名'
       // constantValueキーの値はすべて「Constant」にした想定
       KeyConditionExpression: '#ConstantKey = :constantValue AND #hashKey BETWEEN :startValue AND :endValue',
       ExpressionAttributeNames: {
         '#ConstantKey': 'constantValue'
         '#hashKey': 'partition',
       },
       ExpressionAttributeValues: {
         ':startValue': '50',
         ':endValue': '500',
         ':constantValue': 'Constant',
       }
     }

    const paginator = paginateQuery({ client: docClient }, params)
    let items = []
    for await (const page of paginator) {
      logger.info(`DynamoDB-query-response`, { response: page })
      items = items.concat(page.Items)
      logger.info(`DynamoDB-query-response-merged`, { merged: items })
    }

    console.info({items})
    /* 
     * {
     *   items: [ 
     *     {
     *       userName: 'taro',
     *       partition: '100',
     *       constantValue: 'Constant'
     *     },
     *     {
     *        userName: 'taro',
     *        partition: '200',
     *        constantValue: 'Constant'
     *     },
     *     {
     *        userName: 'jiro',
     *        partition: '300',
     *        constantValue: 'Constant'
     *     },
     *     {
     *        userName: 'saburo',
     *        partition: '350',
     *        constantValue: 'Constant'
     *     }
     *   ]
     * }  
     */

0Like

Comments

  1. @Iyarr

    Questioner

    ありがとうございます。
    テーブル設計について見直す必要があるそうですね
    このテーブルはpartitionとuserNameの両方の複合主キーみたいになっています。
    constantValueを導入した場合、ハッシュキーに割り当てなきゃいけなくてハッシュキーとソートキーによる主キーとしての機能が成り立たなくなってしまいます。その場合でも可能ですか?

    あとアプリケーションの性質上ハッシュキーのみの条件にした場合、6回のクエリでデータを取得することができます。もし有効な手段がなければ、一回のAPIの実行で6回検索させてデータを取得する方法はありなんでしょうか?

    ちなみにそれ以外のところはDynamoDBDocumentClientを使っていてcommandさえ作成出来たら送信してデータが取得できるようにはなっています

    1. 複合主キーを変更する必要はありません。グローバルセカンダリーインデックスを貼って、自分の回答2のようにパーティションキーとソートキーを指定してください。(グローバルセカンダリーインデックスは内部でパーティションキーとソートキーが違うテーブルを複製しますが、開発者が意識するのはキャパシティユニットと容量ぐらいです)

    2. 個人的な意見ですが、6回DynamoDBのAPIを呼ぶのでボトルネックにならないように、また将来的にデータが増えても問題がないか等を検討し実装を決定します。

      • テーブル設計を見直し(グローバルセカンダリーインデックス・ローカルセカンダリーインデックス・パーティションキー・ソートキーが適切なものになっているか)
      • テーブルに格納されるデータ量を計算して、SCANメソッドとプログラミング言語によるフィルターロジックで代替できないか
      • DynamoDBへ複数回のリクエストを行い、必要な情報を得られるか
  2. @Iyarr

    Questioner

    コメントめちゃめちゃ遅くなってすみません!!!
    今現在テーブルを作成する段階でコードはこんな感じになってます

    
    const command = new CreateTableCommand({
          TableName: 'Shifts',
          AttributeDefinitions: [
            { AttributeName: 'userName', AttributeType: 'S' },
            { AttributeName: 'partition', AttributeType: 'S' },
            { AttributeName: 'ConstantKey', AttributeType: 'S' },
          ],
          KeySchema: [
            { AttributeName: 'userName', KeyType: 'HASH' },
            { AttributeName: 'partition', KeyType: 'RANGE' },
          ],
          GlobalSecondaryIndexes: [
            {
              IndexName: 'ConstIndex',
              KeySchema: [
                { AttributeName: 'ConstantKey', KeyType: 'HASH' },
                { AttributeName: 'partition', KeyType: 'RANGE' },
              ],
              Projection: {
                ProjectionType: 'ALL',
              },
              ProvisionedThroughput: {
                ReadCapacityUnits: 5,
                WriteCapacityUnits: 5,
              },
            },
          ],
          BillingMode: 'PROVISIONED',
          ProvisionedThroughput: {
            ReadCapacityUnits: 5,
            WriteCapacityUnits: 5,
          },
        });
    
    

    ただ元々のテーブルの属性とGSIの属性のデータの対応付けができません。
    これはテータを挿入する際にそうなるように明示的に指定する必要があるのでしょうか?

Your answer might help someone💌