初級読了 5 分

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 に畳み込んだりできるプランナも存在しません。

PK = または PK INPK の = / IN なしPartiQL ステートメントSELECT?キー指定の読み取り(スキャンなし)Scan(テーブル全体)INSERT → PutItem(上書きなし)UPDATE →UpdateItem(作成なし)DELETE → DeleteItem

パーティションキーの = も IN もない SELECT はテーブル全体の Scan であり、フィルターが適用される前に読み取ったすべてのアイテムに対して計測されます。コストの計算例で具体的な数字を示しています。自分のテーブルの料金は料金計算ツールで見積もりましょう。

何が違うのか — 機能ごとの比較

Workbench の列が PartiQL の No に対して Yes となっている箇所は、DynoTable の SQL がギャップを埋めます。Workbench は DynamoDB 自身の操作を通して行を読み取り、その上で SQL を実行します。DynamoDB のアクセスパターンのルール内で動く SQLです。

機能標準 SQLDynamoDB PartiQLDynoTable Workbench
JOIN … ON …YesNoYes: INNER / LEFT。ソートキーのないテーブルの PK、またはちょうど 1 つの GSI の PK へ
RIGHT / FULL / CROSS / カンマ結合YesNoNo
自己結合YesNoYes(同じキー付き結合先のルールで)
サブクエリ / 派生テーブルYesNoNo
CTE(WITH …)YesNoNo
UNION / INTERSECT / EXCEPTYesNoNo
GROUP BY / HAVINGYesNoYes
集計(COUNT/SUM/AVG/MIN/MAX)YesNoYes
DISTINCTYesNoYes
CASE / CASTYesNoYes
ウィンドウ関数YesNoNo
ORDER BYYes、任意の列部分的: パーティションキーまたはソートキーのみ、パーティションキーの WHERE が必要Yes、任意の列
LIMITYesインラインでは不可(リクエストの Limit パラメータを使う)Yes
LIKEYesNo(contains / begins_with を使う)Yes
IS NULL / IS NOT NULLYesYes(存在しない属性は NULL ではなく MISSING: IS MISSING を使う)Yes
PK なしの SELECT *スキャン部分的: 暗黙のテーブル全体 ScanYes(実行前に読み取り量の見積もりつき)

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 つのパターンを確認してください。

各カードは、リレーショナル開発者が書こうとする SQL、それに対して DynamoDB PartiQL が実際に行うこと、そしてその理由を示します。「DynoTable で動作」と記されたカードは、Workbench が実行できる同等の SQL を示します。
Joining two tables
PartiQL では不可
SELECT t.ticketId, a.name
FROM tickets t
JOIN accounts a ON t.accountId = a.accountId
GROUP BY and aggregates
PartiQL では不可
SELECT status, COUNT(*) AS tickets, SUM(minutesSpent) AS minutes
FROM tickets
GROUP BY status
Subqueries
PartiQL では不可
SELECT * FROM tickets
WHERE accountId IN (SELECT accountId FROM accounts WHERE country = 'ES')
UNION across tables
PartiQL では不可
SELECT ticketId FROM tickets
UNION
SELECT ticketId FROM archived_tickets
SELECT * (the hidden Scan)
動作するが制約あり
SELECT * FROM tickets
Updating many rows by a filter
PartiQL では不可
UPDATE tickets SET status = 'CLOSED'
WHERE status = 'OPEN'
Quoting string values
動作するが制約あり
SELECT * FROM users WHERE "name" = "Alice"

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 タブを開きましょう。

著者: Dmitriy · 更新日