DynamoDB PartiQL vs SQL: 何が壊れるのか
DynamoDB の PartiQL は SQL の構文を借りていますが、そのエンジンは借りていません。人間も AI アシスタントもリレーショナルな SQL のように書いてしまい、DynamoDB はエラーを返すか、さらに悪いことに「一致なし」に見える空の結果を返します。
DynamoDB の PartiQL は SQL とどう違うのか?
すべての PartiQL ステートメントは、DynamoDB のネイティブなアイテム操作(GetItem、Query、Scan、PutItem、UpdateItem、DeleteItem)にマッピングされます。SQL のように読めますが、それらの操作ができることしかできないため、JOIN、GROUP BY、サブクエリ、集計はありません。
| 記述するもの | DynamoDB が実行するもの |
|---|---|
SELECT … WHERE PK = … | GetItem または Query |
SELECT … WHERE PK IN […] / PK = … OR PK = … | キー指定の読み取り、スキャンなし |
SELECT …(PK の = も IN もなし) | Scan(テーブル全体を読む) |
INSERT INTO … | 挿入専用の PutItem: キーが存在すれば DuplicateItemException |
UPDATE … WHERE PK=… AND SK=… | 既存の 1 アイテムへの UpdateItem: 存在しなければ ConditionalCheckFailedException |
DELETE … WHERE PK=… AND SK=… | DeleteItem(1 アイテム。存在しないキーは削除 0 件で成功) |
PartiQL にはアップサートがありません。上書きまたは作成が必要なときは、ネイティブの PutItem か UpdateItem を呼び出します。また、2 つのテーブルから読み込んだり、ハッシュ結合を構築したり、行を COUNT に畳み込んだりできるプランナも存在しません。
パーティションキーの = も IN もない SELECT はテーブル全体の Scan であり、フィルターが適用される前に読み取ったすべてのアイテムに対して計測されます。コストの計算例で具体的な数字を示しています。自分のテーブルの料金は料金計算ツールで見積もりましょう。
何が違うのか — 機能ごとの比較
Workbench の列が PartiQL の No に対して Yes となっている箇所は、DynoTable の SQL Workbench がギャップを埋めます。Workbench は DynamoDB 自身の操作を通して行を読み取り、その上で SQL を実行します。DynamoDB のアクセスパターンのルール内で動く SQLです。
| 機能 | 標準 SQL | DynamoDB PartiQL | DynoTable Workbench |
|---|---|---|---|
JOIN … ON … | Yes | No | Yes: INNER / LEFT。ソートキーのないテーブルの PK、またはちょうど 1 つの GSI の PK へ |
RIGHT / FULL / CROSS / カンマ結合 | Yes | No | No |
| 自己結合 | Yes | No | Yes(同じキー付き結合先のルールで) |
| サブクエリ / 派生テーブル | Yes | No | No |
CTE(WITH …) | Yes | No | No |
UNION / INTERSECT / EXCEPT | Yes | No | No |
GROUP BY / HAVING | Yes | No | Yes |
集計(COUNT/SUM/AVG/MIN/MAX) | Yes | No | Yes |
DISTINCT | Yes | No | Yes |
CASE / CAST | Yes | No | Yes |
| ウィンドウ関数 | Yes | No | No |
ORDER BY | Yes、任意の列 | 部分的: パーティションキーまたはソートキーのみ、パーティションキーの WHERE が必要 | Yes、任意の列 |
LIMIT | Yes | インラインでは不可(リクエストの Limit パラメータを使う) | Yes |
LIKE | Yes | No(contains / begins_with を使う) | Yes |
IS NULL / IS NOT NULL | Yes | Yes(存在しない属性は NULL ではなく MISSING: IS MISSING を使う) | Yes |
PK なしの SELECT * | スキャン | 部分的: 暗黙のテーブル全体 Scan | Yes(実行前に読み取り量の見積もりつき) |
PartiQL 列の No にはどれも、ネイティブな回避策があります。
JOIN: 各側をキーで Query してコード内でマージするか、1 回の読み取りで両方が返るように非正規化します(JOIN ガイド)。GROUP BYと集計:QueryやScanにSelect: 'COUNT'を付けると一致件数を数えられます(それでも各アイテムは読み取ります)。合計値は書き込み時に維持します(GROUP BY、COUNT と SUM)。- サブクエリ、CTE、
UNION: 各SELECTを個別に実行します。最初の結果のキーをIN [...]リスト(パーティションキー値は最大 50 個)に渡し、結果をコード内でマージします。 - キー以外の列での
ORDER BY、ウィンドウ関数: コード内でソートするか、その順序をソートキーまたは GSI のソートキーとしてモデリングします。 - 必要なときに本物の SQL を: Amazon Athena の DynamoDB コネクタはテーブルに対して結合、集計、ウィンドウ関数を実行できます。Redshift へのゼロ ETL 統合なら、レプリケートされたコピー上でそれらを実行します。それぞれの仕組みは DynamoDB のための SQL を参照してください。
何が壊れるのか、そしてなぜか
いずれも DynamoDB の制約に起因します。
- パーティションキーなしの
SELECT *は隠れたScanです。 PartiQL はデフォルトではエラーを出さず、すべてのアイテムを読み込んでから後でフィルタします。古典的な Query vs Scan の落とし穴です。これをエラーにするには、IAM でdynamodb:FullTableScanがtrueのときにdynamodb:PartiQLSelectを拒否します。 UPDATE/DELETEには完全なプライマリキーが必要です。WHEREはパーティションキー(複合キーテーブルではソートキーも)を確定させなければなりません。status = 'OPEN'のすべての行を更新するには、キーを Query してから、アイテムごとに 1 つのUPDATEをバッチで送ります(実例)。- 二重引用符は文字列ではなく識別子です。
"name"は属性名またはテーブル名で、'name'は文字列値です。値を二重引用符で囲んでもエラーにはなりません。DynamoDB はACCOUNT#acmeという名前の属性と比較し、見つからないのでゼロ行を返します。 INは AWS のドキュメントどおり角括弧で書きます:WHERE pk IN ['a','b']。パーティションキー値は最大 50 個、非キー値は最大 100 個までです。結果は最大 10 アイテムのページで返るので、NextTokenをたどってください。JOINも集計もありません。 テーブルを結合したり行を畳み込んだりするエンジンは存在しません。これは シングルテーブル設計 のトレードオフです。クエリ層が後からデータを再構成できないため、アクセスパターンを前もってモデリングするのです。
なぜ AI アシスタントはこれを間違えるのか
主にリレーショナル SQL で訓練されたモデルは、JOIN、GROUP BY、LIKE、インラインの LIMIT を出力しますが、DynamoDB はこれらを拒否します。文字列リテラルを二重引用符で囲むこともありますが、こちらは拒否されません。"Alice" を属性名として読み、何も返さないのです。
あなたのアシスタントが Postgres のように読める「PartiQL」を書いているなら、空の結果を信用する前にこの 5 つのパターンを確認してください。
DynoTable の SQL Workbench: PartiQL では実行できないクエリ
DynoTable の PartiQL エディターは、入力中に上記のパターンを指摘し、修正を提案します(IN (…) を […] に、LIKE を contains または begins_with に、二重引用符で囲んだ値を単一引用符に)。
JOIN や GROUP BY そのものが必要なときは、SQL Workbench がテーブルから読み取った行に対して 1 つの SELECT を実行します。JOIN のルール、制限、実例は DynamoDB のための SQL にあります。
代わりにネイティブの KeyConditionExpression や FilterExpression の文字列を組み立てるなら、DynamoDB Expression Builder を使ってください。ステートメントの実例は PartiQL の例 にあります。
よくある質問
PartiQL は SQL と同じですか?いいえ。PartiQL は SQL 互換のクエリ言語ですが、DynamoDB 上では GetItem、Query、Scan、PutItem、UpdateItem、DeleteItem ができることしか公開しません。結合、集計、サブクエリ、GROUP BY はありません。
DynamoDB の PartiQL で JOIN はできますか?いいえ。DynamoDB の PartiQL はテーブルを結合できません。各側をキーで Query してコード内で結果をマージするか、Amazon Athena の DynamoDB コネクタを通じて SQL で結合を実行します。DynoTable の SQL Workbench も、結合される側がソートキーのないテーブルのパーティションキー、またはちょうど 1 つの GSI のパーティションキーをキーとしている場合に INNER/LEFT JOIN を実行できます。
DynamoDB の PartiQL は GROUP BY や COUNT をサポートしますか?いいえ。DynamoDB の PartiQL には GROUP BY も COUNT/SUM/AVG もありません。件数には Query や Scan に Select: 'COUNT' を使いますが、一致したアイテムはすべて読み取られます。合計値には、アトミックな ADD で更新するカウンターアイテムを維持するか、Athena や DynoTable の SQL Workbench で SQL を実行します。
なぜ私の SELECT * はこんなにコストがかかるのですか?
WHERE にパーティションキーがないと、PartiQL はテーブル全体の Scan を実行し、フィルターが適用される前に読み取ったすべてのアイテムを計測します。スキャンを避けるには、パーティションキーの = または IN 述語を追加します。
PartiQL では単一引用符と二重引用符のどちらを使うべきですか?文字列値には単一引用符('ACCOUNT#acme')、テーブル名や属性名などの識別子には二重引用符("AppData")を使います。二重引用符で囲んだ値は属性名として読まれるため、クエリは何も言わずに空の結果を返します。
DynamoDB に対して本物の SQL を実行する準備はできましたか?DynoTable をダウンロードして Workbench タブを開きましょう。