AI活用

AIでSQLを作る方法|スキーマ提示・検証・安全な実行まで解説

AIでSQLを作成する手順を、スキーマ提示からEXPLAIN検証までの5段で解説。正常・0件・重複・NULLの4種fixtureの考え方と、read-onlyから始める安全確認チェックリストを掲載します。

この記事の目次
  1. 結論:read-onlyで生成し、fixtureとEXPLAINで検証してから更新系に進む
  2. AIへ渡すスキーマと要件を整える
  3. table・column・型・relationを示す
  4. 期待する行と除外条件を書く
  5. まずSELECTだけを生成する
  6. 使用DBとversionを指定する
  7. 仮定と未確定条件を出力させる
  8. 小さなfixtureで結果を検証する
  9. 正常・0件・重複・NULLを用意する
  10. 期待表と実行結果を比較する
  11. 性能と実行計画を確認する
  12. EXPLAINでscan・join・indexを見る
  13. 速そうに見える書き換えを実測する
  14. UPDATE・DELETEを安全に扱う
  15. transactionとbackupを前提にする
  16. 本番実行は人間の承認から分離する
  17. よくある失敗
  18. スキーマを渡さずに要件だけ伝える
  19. 本番データで「それらしい数字」を確認して終える
  20. SELECTの検証をせずにUPDATEへ進む
  21. よくある質問
  22. スキーマ全体を渡す必要がありますか?
  23. AIが生成したSQLが動いたら、EXPLAINは省略できますか?
  24. EXPLAIN ANALYZE は本番で使っても大丈夫ですか?
  25. AIにUPDATE文を書かせてはいけないのですか?
  26. まとめ
  27. 公式情報

AIでSQLを作成するときは、CREATE TABLE 定義と欲しい結果を先に渡し、まずread-only(読み取り専用)のSELECTだけを生成させ、正常・0件・重複・NULLのfixture(検証用の小さな仮データ)で結果を照合し、EXPLAINで実行計画を確認してから、UPDATE・DELETEに進みます。十分な情報を渡しても生成結果が正しいとは限りません。この順序は、誤りを本番更新まで持ち込む可能性を下げるための検証手順です。

AIはスキーマを見せなければ、テーブル名も列名も推測で埋めます。スキーマと期待する行を明示すると結合や集計の骨格を作りやすくなりますが、正しさを保証するものではありません。この記事では、そのまま使えるサンプルスキーマ、AIへ渡すスキーマ提示テンプレート、基本となる4種のfixtureと期待結果の対応表、そして実行前の安全確認チェックリストを示します。EXPLAINの読み方はMySQL 8.4を前提に説明します。

情報確認日:2026年7月28日(日本時間)

結論:read-onlyで生成し、fixtureとEXPLAINで検証してから更新系に進む

この記事の結論

  • 渡すのはCREATE TABLE・テーブル間の関係・欲しい結果の3点。機密情報は伏せても、型・制約・関係は保つ
  • 最初の生成はSELECTに限定する。UPDATE・DELETE・DDLは同じ会話で書かせない
  • AIには「置いた仮定」と「確認が必要な未確定条件」を必ず併記させる
  • 正常・0件・重複・NULLを基本のfixtureとし、業務固有の境界値も加える。期待結果は先に表で書く
  • EXPLAINでは type・rows・key・Extraを見て、データ量と処理内容に照らして妥当性を判断する
  • UPDATE・DELETEはtransaction対応の表で、同じWHEREのSELECTと件数を確認し、backupと復旧手順も用意する
  • 本番実行はAIの出力から切り離し、人の承認を経た文だけを流す
スポンサーリンク

AIへ渡すスキーマと要件を整える

AIが出すSQLの誤りには、入力不足によるものと、入力が十分でも起きるものがあります。列名の綴り違い、存在しないテーブルの参照、1対多の関係を見落とした結合による行の増殖は、スキーマと関係を明示すると減らせます。それでも、条件の取り違えや不適切な最適化案は残り得るため、後段のfixture検証が必要です。

table・column・型・relationを示す

最も情報を落としにくいのは、CREATE TABLE 文を提示することです。列名だけを箇条書きにすると、型・NULL許容・既定値・インデックスの情報が落ちます。ただし、実際のテーブル名や列名自体が機密なら、名称と値を置き換えた合成スキーマを使い、型・制約・インデックス・テーブル間の関係は保ってください。以下は、この記事全体で使う小さなサンプルスキーマです。

CREATE TABLE users (
  id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  email        VARCHAR(255)    NOT NULL,
  display_name VARCHAR(100)    NOT NULL,
  status       ENUM('active','suspended','withdrawn') NOT NULL DEFAULT 'active',
  created_at   DATETIME        NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uq_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE orders (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id     BIGINT UNSIGNED NOT NULL,
  ordered_at  DATETIME        NOT NULL,
  status      ENUM('pending','paid','cancelled','refunded') NOT NULL,
  coupon_code VARCHAR(32)     DEFAULT NULL,
  PRIMARY KEY (id),
  KEY idx_orders_user_ordered (user_id, ordered_at),
  CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE order_items (
  id         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  order_id   BIGINT UNSIGNED NOT NULL,
  sku        VARCHAR(64)     NOT NULL,
  quantity   INT UNSIGNED    NOT NULL,
  unit_price DECIMAL(10,2)   NOT NULL,
  PRIMARY KEY (id),
  KEY idx_order_items_order (order_id),
  CONSTRAINT fk_items_order FOREIGN KEY (order_id) REFERENCES orders (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

この3表には、検証で問題になりやすい要素を意図的に入れてあります。coupon_code はNULL許容なのでNULL比較の落とし穴を作れ、1注文に複数明細が付くので結合による行の増殖を再現でき、status がENUMなので値の綴り違いが検出できます。自分のスキーマを渡すときも、これらの性質を持つ列があるかを確認してください。テーブル定義そのものの書き方は「MySQLのDDLの書き方」を参照してください。

スキーマに加えて、外部キーだけでは伝わらない関係も言葉で添えます。「1ユーザーに複数注文」「1注文に複数明細」「論理削除はstatusで表現し、行は消さない」といった前提は、結合の書き方を左右します。なお、テーブル名や列名そのものが機密にあたる場合や、社内ルールで外部サービスへの送信が制限されている場合の判断は「AIの安全な使い方」の基準に従ってください。

期待する行と除外条件を書く

要件は「ユーザーごとの売上を出したい」では足りません。最低限、次の4つを決めてから渡します。

  • 1行の意味:「1行=1ユーザー」なのか「1行=1ユーザー×1か月」なのか。これが結果の粒度を決めます
  • 出力する列:列名と型、金額なら税込か税抜か、件数ならキャンセル分を含むか
  • 含める条件と除外する条件:「status が paid のみ」「退会ユーザーは除く」「テスト用アカウントは除く」
  • 期間の境界:「2026年7月」は7月1日00:00:00以上8月1日00:00:00未満なのか、7月31日23:59:59以下なのか。タイムゾーンも明示します

とくに除外条件は、書かなければ意図どおりに除外されません。「キャンセル分が売上に入っていた」という結果は、生成ミスだけでなく、要件に除外条件がないことでも起きます。境界の扱いも同じです。月次集計では、タイムゾーンを揃えたうえで半開区間(以上・未満)を使うと、月末の小数秒や次月との重複を避けやすくなります。

まずSELECTだけを生成する

最初の生成はSELECTに限定します。ただし、SELECTなら無条件に安全という意味ではありません。重いSELECTはデータベースへ負荷をかけ、機密データを読み出す可能性があります。また、FOR UPDATEFOR SHAREを伴うSELECTはロックを取り、INTO OUTFILEINTO DUMPFILEはファイルへ出力します。この記事でいうread-onlyはこれらを含まない通常のSELECTです。読み取り専用アカウントと合成fixtureを使う隔離環境で結果の形を固めてから、必要であれば更新系に進みます。

使用DBとversionを指定する

SQLは製品ごとに方言があります。日付関数、文字列連結、LIMITとOFFSET、ウィンドウ関数の対応状況、UPSERTの書き方は、いずれも製品とversionで変わります。「MySQL 8.4 / InnoDB / 文字セット utf8mb4」のように、製品名・version・ストレージエンジン・文字セットまで指定してください。

あわせて、実行アカウントの権限も伝えます。MySQLのマニュアルでは SELECT 権限について「データベース内のテーブルから行を選択できるようにする」と説明されており、権限付与の方針としては「アカウントには必要な権限だけを付与するのがよい」と明記されています。生成と検証の段階でSELECT権限だけの専用アカウントを使うと、誤って生成されたUPDATE・DELETEの実行を権限エラーで止められます。ただし、読み取り可能なデータの漏えいや重いSELECTの負荷は防げないため、対象データと実行環境の制限も必要です。

次のテンプレートはそのままコピーして使えます。角括弧の中を埋めてください。

あなたはSQLの作成担当です。次の条件で SELECT 文だけを1つ作ってください。

【DB】MySQL 8.4 / InnoDB / 文字セット utf8mb4
【権限】実行アカウントは SELECT のみ。UPDATE・DELETE・DDL は書かない

【スキーマ】(CREATE TABLE をそのまま貼る)

【テーブル間の関係】
- orders.user_id -> users.id(1ユーザーに複数注文)
- order_items.order_id -> orders.id(1注文に複数明細)
- 論理削除は users.status で表現し、行は削除しない

【欲しい結果】
- 1行の意味:
- 出力する列と型:
- 並び順:
- 件数の上限:

【含める条件】
【除外する条件】
【期間とタイムゾーン】(境界は「以上・未満」で書く)
【想定データ量】users 約   行 / orders 約   行 / order_items 約   行

【出力ルール】
1. SQL(1文のみ。説明コメントは SQL の外に書く)
2. この SQL が置いた仮定(スキーマから読み取れなかったこと)
3. 確認が必要な未確定条件(要件の曖昧な点・境界の解釈)
4. 0件・重複・NULL のデータで、このSQLがどう振る舞うか
- スキーマに存在しない table・column を使わない。
  不足があれば「不足している情報」として質問の形で挙げる
- UPDATE・DELETE・DDL は書かない

仮定と未確定条件を出力させる

テンプレートの2と3が、この記事で最も重要な部分です。AIは要件が曖昧でも、何らかの解釈を選んでSQLを書きます。その解釈が明文化されないと、レビューする側は「なぜこの条件が入っているのか」を推測することになります。

実際に返ってくる仮定の例としては、次のようなものがあります。

  • 「売上は quantity * unit_price の合計とし、送料と割引は含めていません」
  • status = 'paid' のみを対象とし、refunded は除外しています」
  • ordered_at はサーバーのタイムゾーンで格納されている前提です」
  • 「同一ユーザーの重複行を避けるため GROUP BY users.id で集約しています」

これらは、読めば正誤を判断できます。書かれていなければ判断のしようがありません。返ってきた仮定に誤りがあれば、その1点だけを訂正して再生成します。SQL全体を作り直させるより、仮定を修正するほうが結果が安定します。

3の未確定条件は、そのまま要件の穴を指しています。「返金された注文を売上に含めるかが決まっていません」と出てきたら、それは仕様として決めるべき事項です。AIに決めさせず、決定してから渡し直してください。

小さなfixtureで結果を検証する

生成されたSQLを本番データで試して「それらしい数字が出た」で終えるのが、最も危険な確認方法です。数十万行の集計結果が正しいかどうかは、目視では判断できません。判断できる大きさまでデータを小さくします。

正常・0件・重複・NULLを用意する

まず4種類のfixtureを作ります。それぞれが別の種類の誤りを検出します。これは出発点であり、期間境界、タイムゾーン、最大・最小値、同額データ、桁あふれ、大きな件数など、対象業務に固有のケースも追加してください。

fixture 入れるデータ 検出したい誤り 期待結果
正常 active なユーザー2名、各1注文(paid)、注文ごとに明細2件 結合と集計の骨格が合っているか 2行。金額は手計算した明細合計と一致する
0件 条件に一致する注文が1件もない期間を指定する 空の結果でエラーにならないか、集計が0かNULLか 結果の粒度に応じて0行、または集約結果1行。SUM が NULL になる設計なら、COALESCE の要否が判明する
重複 1注文に明細3件、同一ユーザーに注文2件を入れる 結合による行の増殖と二重計上 ユーザーは1行のまま。注文件数は COUNT(DISTINCT orders.id) で2になる
NULL coupon_code が NULL の注文、status が withdrawn のユーザー NULL比較の取り違えと、外部結合の欠損側の扱い NULLを「X以外」に含める要件なら、coupon_code IS NULL OR coupon_code <> 'X' などでNULL行が残る。LEFT JOIN の欠損側も要件どおりになる

重複のfixtureは、集計クエリで最も事故が多い箇所を突きます。ordersorder_items を結合したまま COUNT(orders.id) を取ると、明細の数だけ注文が数えられます。3明細の注文が1件あれば、注文件数は3と出ます。少ないデータなら手で数えられるので、この誤りはfixture上ですぐ見つかります。

NULLのfixtureも同様です。SQLでは NULL <> 'X' は真ではなくNULLと評価されるため、その条件だけではクーポンを使っていない注文が結果から消えます。NULLを「X以外」に含める要件なら、coupon_code IS NULL OR coupon_code <> 'X' のように明示します。どちらが正しいかは業務要件で決まるため、期待表にNULL行を残すかどうかを書いてください。

fixtureは本番データのコピーではなく、手で書いた数行を使ってください。本番データを持ち出すと、検証環境やAIサービスへ個人情報が広がります。この線引きは「AIと個人情報の取り扱い」で解説しています。

期待表と実行結果を比較する

順序が重要です。fixtureを入れたら、SQLを実行する前に期待結果を表で書きます。実行してから「この数字で合っていそうだ」と判断すると、出力に引きずられて誤りを見逃します。

期待表は次のような形で十分です。

fixture: 重複
入力
  users:       id=1 (active), id=2 (active)
  orders:      id=10 (user 1, paid), id=11 (user 1, paid), id=12 (user 2, paid)
  order_items: order 10 に3件(合計 3,000円)、order 11 に1件(1,500円)、
               order 12 に2件(800円)

期待結果(並び順: total_amount 降順)
  | user_id | order_count | total_amount |
  |---------|-------------|--------------|
  |       1 |           2 |        4,500 |
  |       2 |           1 |          800 |

実行結果がこの表と違えば、SQL、fixture、期待表、または要件解釈のいずれかを見直します。差分の理由を説明できるまで先へ進みません。基本の4種と業務固有のケースで一致したら、次は性能の確認に進みます。

性能と実行計画を確認する

結果が正しくても、本番のデータ量で実行できなければ使えません。ここではMySQLの EXPLAIN を前提に説明します。他の製品にも実行計画を見る仕組みはありますが、出力形式と用語が異なるため、以下はMySQLの読み方として扱ってください。

EXPLAINでscan・join・indexを見る

MySQL 8.4のマニュアルによれば、EXPLAINSELECTDELETEINSERTREPLACEUPDATETABLE の各文で使えます。通常のEXPLAINは対象文を実行せず、実行計画を表示します。TRADITIONAL形式の出力は1テーブルにつき1行で、列は id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、Extra の順です。読む順番を決めておくと確認が速くなります。

  1. type(結合タイプ):アクセス方法。マニュアルは一般に良いものから system、const、eq_ref、ref、fulltext、ref_or_null、index_merge、unique_subquery、index_subquery、range、index、ALL の順で説明しています。ただし、これは単独の合否スコアではありません。小さな表のALLが妥当なこともあるため、rowsや実データ量と合わせて判断します
  2. key と possible_keys:使えるインデックスの候補と、実際に選ばれたもの。possible_keys に候補があるのに key がNULLなら、条件の書き方でインデックスが使えなくなっている可能性があります
  3. rows:調査される行数の見積り。実測ではなく推定値である点に注意します
  4. Extra:追加情報。ここに最も情報が集まります

Extra で確認する値は次のとおりです。

  • Using filesort:MySQLがソート順を得るために追加のソート処理を必要とする状態。直ちに不具合という意味ではなく、対象件数、並び替えの必要性、実測時間を確認します
  • Using temporary:結果保持用の一時テーブルが必要な状態。GROUP BY と ORDER BY で異なる列を指定した場合などに起きます。必要な集計でも現れるため、件数とメモリ・ディスク使用を含めて評価します
  • Using index:追加の行参照なしにインデックスツリーの情報だけで必要な列を取得できる状態。カバリングインデックスを示しますが、それだけでクエリ全体が速いとは断定できません
  • Using where:WHERE句で行が絞られている状態。マニュアルは、type が ALL または index なのに Extra が Using where でない場合、クエリに問題がある可能性があると注意しています

より正確に見たいときは EXPLAIN ANALYZE があります。これは文を実際に実行し、推定値に加えて実測時間や実測行数を表示します。MySQL 8.4ではSELECT、TABLE、複数テーブルのUPDATE・DELETEを対象にできますが、このワークフローでは更新系に対して使いません。UPDATE・DELETEなら実データが変わるためです。十分に検証した通常のSELECTだけを、原則として本番相当の複製環境やステージングで実行します。SELECTでも負荷やロック待ちを起こし得るため、本番での実行はクエリタイムアウト、監視、実行時間帯などの運用基準に従ってください。EXPLAIN ANALYZEの出力形式はTREEです。

速そうに見える書き換えを実測する

AIに「このクエリを速くして」と頼むと、書き換え案がいくつも返ってきます。サブクエリを結合に変える、INEXISTS に変える、DISTINCTGROUP BY に変える、といったものです。これらは一般論としては妥当なこともありますが、実際に速くなるかはデータの分布とインデックスの構成で決まります。

判断は必ず実測で行います。手順は次のとおりです。

  1. 書き換え前と書き換え後で、両方のEXPLAINを取って並べる
  2. type、key、rows、filtered、Extraがどう変わったかを見て、その変化がデータ量と処理内容に対して妥当か判断する
  3. 結果が同じであることを、4種のfixtureで再確認する(書き換えで結果が変わることは珍しくありません)
  4. 本番相当のデータ量を持つ環境で、実行時間を数回測って比べる

3を飛ばすと、速いが結果の違うクエリができあがります。とくに NOT INNOT EXISTS の入れ替えは、NULLが含まれる列で結果が変わり得るため、NULLのfixtureでの再確認が欠かせません。探索的なSELECTに LIMIT を付けると返却行数は抑えられますが、集計や並べ替えなど、結果を返す前の処理量まで必ず減るわけではありません。LIMITを負荷対策の代わりにはせず、EXPLAINと実測で判断してください。

UPDATE・DELETEを安全に扱う

ここからは、誤ると復旧が難しくなる領域です。SELECTで確認した条件を基にし、対象表のストレージエンジン、トリガー、外部キーの連鎖動作、同時更新の有無まで確認してから実行します。トランザクションだけに頼らず、backupと復旧手順も用意します。

transactionとbackupを前提にする

MySQLは既定でautocommitが有効です。明示的なトランザクション内にいない場合、各文はそれぞれ確定するため、DELETEを実行した後からROLLBACKしても取り消せません。また、トランザクションで戻せるのはInnoDBなどトランザクション対応の表に対する変更です。backupはROLLBACKの代替ではなく、誤ったCOMMIT、DDL、障害などに備える別の防御です。

更新系の実行手順は次のように固定します。

  1. 対象表がトランザクション対応か、トリガーや外部キーで別の表へ影響しないかを確認する
  2. 同じWHERE句で SELECTSELECT COUNT(*) を実行し、対象行と件数を確認する。JOINを使う更新では、更新対象の主キー一覧も確定する
  3. backupの取得時刻、復旧手順、必要ならバイナリログによるポイントインタイムリカバリの可否を確認する。復旧手順は事前に別環境で試す
  4. 同時更新によって対象が変わり得るなら、メンテナンス時間帯、適切なロック、主キーを固定した更新などの方針を決める。長時間のトランザクションは避ける
  5. START TRANSACTION; を実行し、必要に応じて同じトランザクション内でも対象を再確認する
  6. UPDATEまたはDELETEを実行し、クライアントの影響行数、ROW_COUNT()、更新後の主キーと値を確認する
  7. 意図した状態なら COMMIT;、少しでも違えば ROLLBACK;する。COMMIT後も件数・実行時刻・実行文を記録する

事前のCOUNT(*)とUPDATEの影響行数は、常に単純一致するとは限りません。MySQLのUPDATEでは、条件に一致した行数と実際に値が変わった行数が異なることがあり、クライアントの接続設定でも表示の意味が変わります。同じ値を再設定した行は変更行に数えられない場合があります。件数だけで合否を決めず、使用クライアントの仕様を確認し、更新対象の主キーと更新後の値も照合してください。

CREATE TABLEALTER TABLEなど、多くのDDLは暗黙のコミットを引き起こします。トランザクション内にDDLを混ぜると、その前の変更が確定することがあります。CREATE TEMPORARY TABLEのように暗黙コミットの例外でも、文自体はROLLBACKされないものがあります。更新系とDDLは別の作業として計画してください。

補助的な防御として、mysqlクライアントの--safe-updates-U--i-am-a-dummy)があります。有効にすると、WHERE句でキーを使わず、LIMITもないUPDATE・DELETEなどをエラーにできます。ただし、キーを使った誤条件や、小さいLIMITを伴う誤更新までは防げません。レビュー、最小権限、トランザクション、backupの代わりではなく、追加のガードとして使います。

本番実行は人間の承認から分離する

最後は運用の設計です。AIが生成したSQLが、そのまま本番データベースへ流れる経路を作らないでください。具体的には、生成する場所と実行する場所を分けます。

  • 生成と検証は、SELECT権限のみのアカウントで、複製環境に対して行う
  • 更新系の文は、レビュー対象の成果物として扱う(差分レビューを通す)
  • 本番で実行するのは、承認されたSQLファイルだけ。会話の出力から直接コピーしない
  • AIエージェントに本番データベースへの書き込み権限を直結させない
  • 実行者と承認者を分け、実行した文・件数・時刻を記録する

人が書いたSQLにも誤りは起きますが、AIの出力には、もっともらしい存在しない列や暗黙の仮定が混ざる可能性があります。生成が速いほどレビュー待ちのSQLも増えるため、生成経路と本番実行経路の間に、証跡の残る人の確認を置きます。

公開前・実行前の確認は、次のチェックリストで機械的に行ってください。

## 生成直後(read-only の確認)
- [ ] 文の種類は SELECT だけか(UPDATE / DELETE / DDL が混ざっていないか)
- [ ] FOR UPDATE / FOR SHARE / INTO OUTFILE / INTO DUMPFILE など、ロックや外部出力を伴う句がないか
- [ ] スキーマに実在する table・column だけを使っているか
- [ ] AI が出した「置いた仮定」をすべて読み、誤りがないか
- [ ] 「確認が必要な未確定条件」が残っていないか
- [ ] 期間の境界は「以上・未満」で書かれ、タイムゾーンが揃っているか

## fixture 検証
- [ ] 正常・0件・重複・NULLに加え、業務固有の境界値を実行したか
- [ ] 実行前に期待結果の表を書き、それと突き合わせたか
- [ ] 結合後の行数が意図どおりか(DISTINCT で誤魔化していないか)
- [ ] NULL の扱いを明示したか(IS NULL / COALESCE / <> の挙動)
- [ ] fixture は手で書いたデータか(本番データのコピーではないか)

## 実行計画
- [ ] EXPLAIN の type・key・rows・filtered・Extra を確認したか
- [ ] ALL / Using filesort / Using temporary が出た場合、データ量と処理内容に照らして妥当性を説明できるか
- [ ] rows の見積りが実データ量と比べて妥当か
- [ ] LIMITだけを負荷対策にせず、実測または運用上の実行制限を確認したか

## 更新系を実行する場合
- [ ] 表がトランザクション対応か、trigger・外部キーの連鎖動作を確認したか
- [ ] 同じ WHERE で対象の主キーと SELECT COUNT(*) を取り、件数を確認したか
- [ ] 同時更新に対するロック・実行時間帯・対象固定の方針を決めたか
- [ ] backup の取得時刻と、検証済みの復旧手順を確認したか
- [ ] START TRANSACTION で囲み、COMMIT前ならROLLBACKできる状態か
- [ ] 使用クライアントの影響行数の意味を確認し、更新後の主キーと値も照合したか
- [ ] トランザクション内に DDL を混ぜていないか
- [ ] --safe-updates を補助ガードとして有効にできるか
- [ ] 実行アカウントの権限は必要最小限か
- [ ] 承認者と実行者を分け、実行内容を記録したか

よくある失敗

スキーマを渡さずに要件だけ伝える

「注文テーブルから月次売上を出して」とだけ伝えると、AIは一般的な名前のテーブルと列を仮定してSQLを書きます。動かないだけならまだ良く、たまたま似た名前の列が存在すると、意味の違う値を集計してしまいます。CREATE TABLE を貼るのが最短で確実です。

本番データで「それらしい数字」を確認して終える

大きなデータの集計結果は、正誤を目視で判断できません。二重計上や条件漏れは、合計が数パーセント増える程度の変化として現れ、見た目では気づけないためです。手で数えられる大きさのfixtureで、期待表と突き合わせてください。

SELECTの検証をせずにUPDATEへ進む

WHERE句の誤りは、SELECT では「余分な行が表示される」で済みますが、UPDATE では「余分な行が書き換わる」になります。同じ条件で先にSELECTを実行し、件数と対象行を確認する手順を飛ばさないでください。トランザクションで囲んでいても、COMMIT してしまえば同じです。

よくある質問

スキーマ全体を渡す必要がありますか?

通常は関係する範囲に絞ります。ただし、参照先だけでなく、ビュー、トリガー、外部キー、ストアドルーチンなど、結果や更新に影響する依存関係は省けません。機密名は置き換えつつ、型・制約・関係を保った合成スキーマを用意してください。

AIが生成したSQLが動いたら、EXPLAINは省略できますか?

fixtureは小さいため、非効率なクエリでも問題が見えないことがあります。動いたことは性能の保証になりません。本番相当のデータ量でフルテーブルスキャンやソートが問題になる可能性があるため、EXPLAINで type・key・rows・filtered・Extraを確認する手順は残してください。

EXPLAIN ANALYZE は本番で使っても大丈夫ですか?

原則として、本番相当の複製環境やステージングを使います。EXPLAIN ANALYZEは対象文を実行するため、SELECTでも高負荷やロック待ちを起こし得ます。本番で必要なら、十分に検証した通常のSELECTに限定し、クエリタイムアウト、監視、実行時間帯などの運用基準に従ってください。更新系には使いません。

AIにUPDATE文を書かせてはいけないのですか?

隔離環境でレビュー用の候補を書かせることはできます。危険なのは、生成したその場で本番に実行することです。SELECTで対象を確認し、fixture、実行計画、人のレビュー、最小権限、トランザクション、backupを通してもリスクはゼロになりません。承認済みのSQLファイルだけを別の実行経路へ渡してください。

まとめ

AIでSQLを作成する作業は、CREATE TABLE と関係と欲しい結果を渡すところから始まります。要件が曖昧なまま渡すと、AIは何らかの解釈を選んで書いてしまうため、置いた仮定と未確定条件を必ず併記させ、仮定の誤りだけを訂正して再生成します。

検証は、正常・0件・重複・NULLを基本のfixtureとし、業務固有の境界値を追加します。実行前に期待結果の表を書いてから突き合わせます。性能はMySQLのEXPLAINで type・key・rows・filtered・Extraを確認し、書き換え案は必ず結果の再検証と実測を通します。ALLやUsing filesortがあるだけで機械的に不合格にせず、データ量と処理内容に照らして評価してください。

UPDATE・DELETEは、対象表のエンジン、トリガー、外部キー、同時更新を確認し、backupと検証済みの復旧手順を用意したうえでトランザクション内で実行します。既定のautocommit、DDLの暗黙コミット、影響行数の意味の違いを把握し、--safe-updatesは補助ガードとして使います。生成の速度が上がったぶん、その手前に人の承認と実行記録を置いてください。

公式情報

スポンサーリンク