初級読了 5 分

DynamoDB PartiQL vs SQL: 何が壊れるのか

DynamoDB の PartiQL をめぐる混乱の最大の原因は — 人間にとっても AI アシスタントにとっても — それをリレーショナルな SQL として扱ってしまうことです。PartiQL はそうではありません。PartiQL は DynamoDB の既存の操作の上に載るSQL 互換のサーフェスであって、結合・グループ化・集計ができるクエリエンジンではありません。おなじみのキーワードの下には、まったく異なる仕組みが隠れています。

DynamoDB の PartiQL は SQL とどう違うのか?

PartiQL は SQL の構文を借りていますが、そのエンジンは借りていません。DynamoDB ではすべてのステートメントが単一のネイティブ操作 — GetItemQueryScanPutItemUpdateItemDeleteItem のいずれか — にマッピングされるため、JOINGROUP BY、サブクエリ、集計は存在しません。SQL のように読めますが、それらのキーバリュー操作がもともとできることしかできないのです。

すべての PartiQL ステートメントは、DynamoDB のネイティブ操作のいずれかにコンパイルされます。

記述するものDynamoDB が実行するもの
SELECT … WHERE PK = …GetItem または Query
SELECT … (PK なし)Scanテーブル全体を読む)
INSERT INTO …PutItem
UPDATE … WHERE PK=… AND SK=…UpdateItem(1 アイテム)
DELETE … WHERE PK=… AND SK=…DeleteItem(1 アイテム)

2 つのテーブルから読み込んだり、ハッシュ結合を構築したり、行を COUNT に畳み込んだりできるプランナは存在しません。ある操作が単一の Get/Query/Scan/Put/Update/Delete にマッピングされなければ、PartiQL はそれを表現できません。話はこれで全部です — 以下はすべて、この 1 つの事実の帰結にすぎません。

同じマッピングをフローとして見ると — WHERE 句が SELECT を安価な Query にするか、テーブル全体の Scan にするかを決めます。

WHERE pins full PKno PK in WHEREPartiQL statementSELECT?Query (one partition)Scan (whole table)INSERT PutItemUPDATE UpdateItemDELETE DeleteItem

各ステートメントは正確に 1 つのネイティブ操作に解決されます — この 1 対 1 のマッピングこそが、PartiQL が結合・グループ化・集計できない理由です。

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

Workbench の列が PartiQL の No に対して Yes となっている箇所は、そのすべてが DynoTable の SQL が埋めるギャップです。Workbench はテーブルを DynamoDB の本物のクエリランタイムを通してマテリアライズし、その上で本物の SQL を実行します — DynamoDB のアクセスパターンのルール内で動く SQLです。

機能標準 SQLDynamoDB PartiQLDynoTable Workbench
JOIN … ON …YesNoYes — INNER / LEFT(PK または GSI パーティションキーへ)
RIGHT / FULL / CROSS / カンマ結合YesNoNo
自己結合YesNoNo(まだ未対応)
サブクエリ / 派生テーブルYesNoNo
CTE(WITH …YesNoNo
UNION / INTERSECT / EXCEPTYesNoNo
GROUP BY / HAVINGYesNoYes
集計(COUNT/SUM/AVG/MIN/MAXYesNoYes
DISTINCTYesNoYes
CASE / CASTYesNoYes
ウィンドウ関数YesNoNo
ORDER BYYes、任意の列部分的 — ソートキーのみ(パーティションキーの WHERE が必要)Yes、任意の列
LIMITYesインラインでは不可(リクエストの limit パラメータを使う)Yes
LIKEYesNo(contains / begins_with を使う)Yes
IS NULL / IS NOT NULLYesYes(存在しない属性は NULL ではなく MISSINGIS MISSING を使う)Yes
PK なしの SELECT *スキャン部分的 — 暗黙のテーブル全体 ScanYes(コストの可視化つき)

何が壊れるのか、そしてなぜか

これらは、クエリがワイヤに届く前に DynoTable の PartiQL バリデータが検出する失敗です — それぞれが実際の DynamoDB の制約に起因します。

  • なしの SELECT * は隠れた Scan です。 PartiQL はエラーを出さず、 すべてのアイテムを読み込んでから後でフィルタします。これは親しみやすい構文の裏に潜む、古典的な Query vs Scan のコストの落とし穴です。
  • UPDATE / DELETE には完全なプライマリキーが必要です。 これらは単一アイテムの UpdateItem/DeleteItem にマッピングされるため、WHERE はパーティションキー( テーブルではソートキーも)を確定させなければなりません。「status が 'open' のすべての行を更新する」 ことを 1 つのステートメントで行うことはできません。
  • 二重引用符は文字列ではなく識別子です。 DynamoDB の PartiQL はここで SQL 標準に従います。 "name" は列名/テーブル名で、'name' は文字列値です。値を二重引用符で囲むのは初心者に 最も多いミスです — バリデータのメッセージは文字どおり "Double quotes delimit identifiers in DynamoDB PartiQL, not strings. Use single quotes for string values." です。
  • IN は丸括弧ではなく角括弧を使います: WHERE pk IN ['a','b']。PK 値は最大 50 個、 非キー値は最大 100 個までです。
  • JOIN も集計もありません。 テーブルを結合したり行を畳み込んだりするエンジンは存在しません。 これは シングルテーブル設計 のトレードオフです。クエリ層が後から データを再構成できないため、アクセスパターンを前もってモデリングするのです。

なぜ AI アシスタントはこれを間違えるのか

LLM は膨大な量のリレーショナル SQL で訓練されているため、DynamoDB に対しても自信を持って JOINGROUP BYLIKE、インラインの LIMIT、二重引用符で囲んだ文字列リテラルを吐き出します — その すべてを DynamoDB は拒否します。DynoTable 独自のモデルクエリ自動修正が存在するのは、まさに安価な モデルがこれらのパターンを確実に生成するからです。二重エスケープされた引用符を取り除き、 LIKE '%x%'containsIS NULLattribute_not_exists に書き換え、インラインの LIMIT を リクエストパラメータへ引き上げます。あなたの AI が Postgres のように読める「PartiQL」を生成している なら、それが見分けの印です。

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

DynoTable の SQL Workbench: PartiQL では実行できないクエリ

本当に JOINGROUP BY が必要になったとき、その答えが DynoTable の SQL Workbench です。 各 JOIN の結合先をパーティションキーに対して検証し、結合された行を DynamoDB の本物の Query/Scan ランタイムを通してマテリアライズし、その上で単一の SELECT(集計、GROUP BYDISTINCTCASECAST)を実行します — DynamoDB のアクセスパターンのルール内で動く SQLです。

-- Runs in the DynoTable Workbench (NOT in PartiQL):
SELECT c.country, COUNT(*) AS orders, SUM(o.total) AS revenue
FROM orders o
INNER JOIN customers c ON o.customerId = c.PK
GROUP BY c.country
ORDER BY revenue DESC

正直な制約(Workbench は DynamoDB のアクセスモデルを強制し、Postgres のふりはしません):

  • INNER JOINLEFT JOIN のみ — ON の結合先属性はパーティションキーまたは GSI パーティション キーでなければなりません。RIGHT / FULL / CROSS / カンマ結合はありません。
  • 自己結合はまだ、サブクエリ、派生テーブル、ウィンドウ関数もありません。
  • 結合と射影はスカラー属性に対して動作します。

生の API 向けに条件式とキー式を組み立てるだけでよいなら、 DynamoDB Expression Builder が PartiQL のサーフェスを まったく使わずに正しい FilterExpression / KeyConditionExpression を生成します。PartiQL を正しく 使うには、実例つきの PartiQL の例 を参照してください。任意のクエリの コストを見積もるには アイテムサイズ計算ツール を使いましょう。 PartiQL がワイヤフォーマットを変えることは決してなく、値は依然として DynamoDB-JSON として送られる点に注意してください。クライアントを選んでいますか? プレーンな DynamoDB GUIDynobase に対して Workbench がどこに位置づけられるかを確認してください。

FAQ

PartiQL は SQL と同じですか? いいえ。PartiQL は SQL 互換のクエリ言語ですが、DynamoDB 上では単一の Get/Query/Scan/Put/Update/Delete にマッピングされる操作しか公開しません。結合、集計、サブクエリ、 GROUP BY はありません。

DynamoDB の PartiQL で JOIN はできますか? いいえ。DynamoDB の PartiQL はテーブルを結合できません。DynoTable の SQL Workbench は、データを DynamoDB の本物のクエリランタイムを通してマテリアライズすることで、INNER/LEFT JOIN (パーティションキーまたは GSI パーティションキーへ)を実行できます。

DynamoDB の PartiQL は GROUP BY や COUNT をサポートしますか? いいえ — DynamoDB の PartiQL には集計も GROUP BY もありません。COUNT/SUM/AVG/GROUP BY/HAVING のクエリには DynoTable の SQL Workbench を使ってください。

なぜ私の SELECT * はこんなにコストがかかるのですか? WHERE にパーティションキーがないと、PartiQL はテーブル全体の Scan を実行し、フィルタが適用される 前にすべてのアイテムの読み取りを計上します。パーティションキーの述語を追加して Query に変えましょう。

PartiQL では単一引用符と二重引用符のどちらを使うべきですか? 文字列値には単一引用符('CUSTOMER#42')、テーブル名や属性名などの識別子には二重引用符 ("AppData")を使います。値を二重引用符で囲むのが最も多い PartiQL のミスです。

DynamoDB に対して本物の SQL を実行する準備はできましたか?DynoTable をダウンロードして Workbench タブを開きましょう。

更新日