はじめに

AIにSQLを書かせる機会が、ここ一年で一気に増えました。「先月の売上を商品カテゴリ別に出して」と日本語で頼めば、それらしいSQLがすぐに返ってきます。実際に動きますし、結果もそれっぽい数字が並びます。

問題は、その「それっぽい」が曲者だということです。

SQLがエラーなく実行できることと、出てきた数字が正しいことは、まったく別の話です。AIが生成したSQLは、文法的には完璧でも、集計の対象を取り違えていたり、結合の条件が一つ足りなかったり、重複を数え込んでいたりします。しかも見た目はちゃんと動くので、レビューする側もうっかり信じてしまいます。

中小ECの現場で、AIが出した「先月の粗利」を信じて施策を打ったら、実は税込・税抜が混ざっていて数字が二割ずれていた、ということが起きます。動くけれど間違っているSQLは、エラーで止まるSQLよりも怖いのです。

そこで、AIが生成したSQLの結果が正しいかどうかを、BigQuery上で機械的に検証する仕組みを作りました。この記事では、その検証の観点と、実際に使っているクエリの形を紹介します。

📍 この記事の位置づけ

ここでは「AIが生成したSQLの結果が正しいか」を検証する一般的な枠組みを扱います。コストを下げる目的でAIにクエリの改善を頼む場面については、AIにBigQueryのクエリをレビューさせてコストを削減した方法で扱っています。あちらの記事にも「検算」のステップがありますが、それをより一般的な検証の枠組みまで広げたのが本記事だと捉えてもらえればと思います。

なぜ「動く」だけでは足りないのか

SQLのレビューには、大きく二つの段階があります。

一つはコードとしての正しさです。文法エラーがないか、参照しているテーブルやカラムが存在するか。これはBigQueryが実行時に教えてくれますし、ドライランで事前に確認することもできます。

もう一つは結果としての正しさです。出てきた数字が、本来計算したかったものと一致しているか。こちらは実行できただけでは分かりません。AIが生成したSQLで事故が起きるのは、ほぼこちらの段階です。

人間が書いたSQLなら「自分はこういう意図で書いた」という前提を持ってレビューできますが、AIが書いたSQLにはその前提がありません。意図を読み取りながら、本当にその意図どおりに動いているかを外から確認する必要があります。これを毎回手作業でやるのは現実的ではないので、検証を仕組みにしました。

検証の四つの観点

検証フレームワークと言っても、難しいものではありません。次の四つの観点でチェッククエリを用意しているだけです。

件数のチェック

まず、行数とユニーク数を見ます。集計前のテーブルの件数と、集計後の合計が辻褄が合っているか。本来一意であるはずのキーに重複がないか。

AIが生成したSQLでもっとも多い事故が、結合によるレコードの増殖です。一対多の関係を持つテーブルを安易にJOINすると、親側の行が子の数だけ膨らみ、金額を合計したときに過大になります。件数チェックはこれを一番早く捕まえられます。

既知値との一致

次に、答えが分かっている値と突き合わせます。「先月の総注文件数は1,284件」のように、別の経路で確定している数字があれば、それと一致するかを確認します。

経理が締めた売上、管理画面が表示している注文数、過去に手で集計した実績。こうした「正しいと分かっている値」を一つでも持っておくと、検証の基準点になります。

別の計算方法での突合

同じ数字を、わざと別のロジックで計算して比べます。たとえば売上合計を「注文明細を足し上げた値」と「注文ヘッダの金額を足し上げた値」の二通りで出して、差がゼロになるかを見ます。

二つの独立した計算が同じ答えになれば、両方が同じ間違いをしている可能性はぐっと下がります。AIに元のSQLを書かせたなら、検証用の別ロジックは人間が書くか、別のAIに書かせると独立性が保てます。

境界とNULLのチェック

最後に、端っこを確認します。期間の境界が「以上・未満」で正しく区切られているか。NULLが意図せず除外されたり、逆に混ざり込んだりしていないか。マイナス値やゼロのような異常値が紛れていないか。

AIは「先月」のような曖昧な指定を、月初0時から月末23時59分までと解釈したり、翌月1日0時を含めてしまったりと、扱いがぶれます。境界は事故が集まりやすい場所なので、明示的に確認します。

検証クエリの例

実際の検証クエリの形を示します。ここでは注文テーブル orders と注文明細 order_items があり、AIが「先月の売上合計」を出すSQLを生成した、という想定です。

まず件数と重複のチェックです。注文IDが一意であるべきなのに重複していないかを見ます。

SELECT
  COUNT(*) AS total_rows,
  COUNT(DISTINCT order_id) AS unique_orders,
  COUNT(*) - COUNT(DISTINCT order_id) AS duplicate_count
FROM `myproject.sales.orders`
WHERE order_date >= '2026-06-01'
  AND order_date <  '2026-07-01';

duplicate_count がゼロでなければ、どこかで行が増えています。AIのSQLがこのテーブルをJOINしているなら、まずここを疑います。

次に、別の計算方法での突合です。売上合計を注文ヘッダ側と明細側の二通りで計算し、差を見ます。

WITH header_total AS (
  SELECT SUM(total_amount) AS amount
  FROM `myproject.sales.orders`
  WHERE order_date >= '2026-06-01'
    AND order_date <  '2026-07-01'
),
item_total AS (
  SELECT SUM(oi.unit_price * oi.quantity) AS amount
  FROM `myproject.sales.order_items` AS oi
  JOIN `myproject.sales.orders` AS o
    ON oi.order_id = o.order_id
  WHERE o.order_date >= '2026-06-01'
    AND o.order_date <  '2026-07-01'
)
SELECT
  header_total.amount AS header_amount,
  item_total.amount   AS item_amount,
  header_total.amount - item_total.amount AS diff
FROM header_total, item_total;

diff がゼロなら、二つの独立した計算が一致したことになります。ゼロでなければ、どちらかの集計、あるいは元のSQLのロジックに問題があります。値引きや送料の扱いで差が出ることも多く、その場合は「なぜ差が出るのか」を説明できるかどうかが判断の分かれ目になります。

最後に、境界とNULLのチェックです。期間指定が正しく効いているか、金額にNULLや異常値が混ざっていないかを確認します。

SELECT
  MIN(order_date) AS min_date,
  MAX(order_date) AS max_date,
  COUNTIF(total_amount IS NULL) AS null_amount,
  COUNTIF(total_amount < 0)     AS negative_amount,
  COUNTIF(total_amount = 0)     AS zero_amount
FROM `myproject.sales.orders`
WHERE order_date >= '2026-06-01'
  AND order_date <  '2026-07-01';

min_datemax_date が想定の期間に収まっているか。null_amount が意図しない値になっていないか。マイナス金額が紛れていないか。ここで違和感があれば、AIの解釈が自分の意図とずれていたサインです。

AI生成から自動検証までを回す

これらの検証クエリは、一度きりで終わらせず、運用に組み込むと効いてきます。流れはシンプルです。

まず、AIに分析用のSQLを生成させます。次に、そのSQLが対象としているテーブルとキーに対して、件数・突合・境界の検証クエリを実行します。検証クエリ自体はテーブルとキーの名前を差し替えれば使い回せるので、テンプレートとして持っておきます。

検証の合否は数値で判定できます。重複件数がゼロか、突合の差がゼロか、境界が想定内か。これらをまとめて一本のクエリにして、すべての条件を満たしたときだけ PASS を返すようにしておくと、目視に頼らず判断できます。

WITH checks AS (
  SELECT
    (SELECT COUNT(*) - COUNT(DISTINCT order_id)
     FROM `myproject.sales.orders`
     WHERE order_date >= '2026-06-01'
       AND order_date <  '2026-07-01') AS dup,
    (SELECT COUNTIF(total_amount < 0)
     FROM `myproject.sales.orders`
     WHERE order_date >= '2026-06-01'
       AND order_date <  '2026-07-01') AS negatives
)
SELECT
  IF(dup = 0 AND negatives = 0, 'PASS', 'FAIL') AS result,
  dup,
  negatives
FROM checks;

FAIL が出たSQLは、結果を使う前に必ず人間が中身を確認します。PASS が出ても、既知値との一致だけは別途確認しておくと安心です。完全に自動で任せきりにするのではなく、AIが速く下書きを出し、検証が機械的にふるいにかけ、最後の判断を人間が下す。この分担がいまのところ一番うまくいっています。

検証の考え方そのものは、AIを使うかどうかに関わらず通用します。SQLのテストとデータ品質の基本については、別の記事でより一般的にまとめています。あわせて読むと、検証クエリをどう設計するかのイメージがつかみやすいはずです。

まとめ

AIが生成したSQLは、エラーなく動くからといって正しいとは限りません。文法の正しさと結果の正しさは別物で、事故はほぼ後者で起きます。

そこで、四つの観点で検証する仕組みを用意しました。件数で重複や増殖を捕まえ、既知値との一致で基準点を確認し、別の計算方法で突合し、境界とNULLで端っこを確かめる。どれも特別な技術ではなく、テンプレート化した検証クエリを差し替えながら回すだけです。

AIに速さを任せ、検証で安全を担保し、判断は人間が握る。この分担を守れば、AIが書いたSQLも安心して仕事に使えます。「動いたから大丈夫」で止まらず、「合っているか」をもう一段だけ確認する習慣を、仕組みとして持っておくことをおすすめします。

自社のデータでこの検証の仕組みを組み込みたい、あるいはAI活用も含めたBigQuery分析基盤そのものを整えたいという場合は、BigQueryデータ基盤構築サービスでご相談いただけます。まずは状況を話してみたいという方は、お問い合わせからどうぞ。