業務用スクリプトの依頼文に入力・出力・エラー処理のルールを入れる
「これを自動化して」を、実行可能な task description に変えます。sensitive data を含まない合成 sample と、手作業で確認した expected result を添えて、team ごとの work logs を集計する script request を完成させます。
AIが生成したSQLクエリは完成版ではなく下書きとして扱い、正解が分かっている小さなSQLiteデータベースでテストします。実データに使う前に、AIの結果を手計算した期待表と比較します。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English
対象読者AIを使ってSQLの下書きを作成し、重要なデータで実行する前に小さく再現可能なテストを行いたい人向けです。
要件が「すべての顧客について、2026年8月に発生したPAID注文の合計金額を報告する。条件に合う注文がない顧客も合計0で表示する」だとします。
AIアシスタントがcustomersとordersをINNER JOINし、その後WHEREでstatusとdateを絞り込むクエリを提案したとします。見た目は妥当に見えますが、INNER JOINでは一致する注文行がない顧客が削除されます。これは合計0の顧客も含めるという要件に反します。
以下のデータベースは合成データで、このページ専用に作成したものです。顧客3人と注文6件があり、PAIDと非PAID、月の境界、8月にPAID注文がない顧客を確認できるように構成しています。
import sqlite3
from pathlib import Path
SOURCE_DIR = Path("outputs") / "ai_sql_demo"
DATABASE = SOURCE_DIR / "orders.sqlite"
CUSTOMERS = [
(1, "Ana"),
(2, "Ben"),
(3, "Cara"),
]
ORDERS = [
(101, 1, "2026-08-03", "PAID", 40),
(102, 1, "2026-08-20", "PAID", 60),
(103, 1, "2026-08-25", "CANCELLED", 30),
(104, 2, "2026-08-10", "PAID", 25),
(105, 2, "2026-09-01", "PAID", 50),
(106, 3, "2026-08-12", "CANCELLED", 80),
]
SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir() # Stop if the synthetic source already exists.
connection = sqlite3.connect(DATABASE)
try:
connection.executescript("""
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
customer_name TEXT NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL,
amount INTEGER NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
""")
connection.executemany(
"INSERT INTO customers VALUES (?, ?)", CUSTOMERS
)
connection.executemany(
"INSERT INTO orders VALUES (?, ?, ?, ?, ?)", ORDERS
)
connection.commit()
finally:
connection.close()
python create_ai_sql_demo.pyAnaには8月のPAID注文が40と60の2件あるため、合計は100です。CANCELLED注文は数えません。Benには対象となる8月の注文が25の1件あり、9月の注文は数えません。Caraには8月のCANCELLED注文しかないため、それでも0として表示される必要があります。
| customer_id | customer_name | 期待される8月のPAID合計 |
|---|---|---|
| 1 | Ana | 100 |
| 2 | Ben | 25 |
| 3 | Cara | 0 |
期待される総合計は125です。要件で全顧客を表示するとしているため、出力行は正確に3行である必要があります。
以下のAIクエリはINNER JOINの後、WHERE句で対象注文を絞り込みます。Caraには条件に合う8月のPAID注文がないため、結果から完全に消えます。
SELECT
c.customer_id,
c.customer_name,
SUM(o.amount) AS august_paid_total
FROM customers AS c
JOIN orders AS o
ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
AND o.order_date >= '2026-08-01'
AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;修正版クエリはすべての顧客から始め、LEFT JOINを使います。注文のフィルタ条件をON句の中に置くことで、一致する注文がない顧客も残ります。COALESCEはその結果のNULL集計値を0に変換します。
SELECT
c.customer_id,
c.customer_name,
COALESCE(SUM(o.amount), 0) AS august_paid_total
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.customer_id
AND o.status = 'PAID'
AND o.order_date >= '2026-08-01'
AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;以下のスクリプトをai_sql_check.pyとして保存してください。2つのクエリを実行し、その結果を手計算した期待表と比較し、CSVレビュー報告を書き出します。合成データベース自体は変更しません。
import csv
import sqlite3
from pathlib import Path
DATABASE = Path("outputs") / "ai_sql_demo" / "orders.sqlite"
OUTPUT_DIR = Path("outputs") / "ai_sql_check_result"
REPORT = OUTPUT_DIR / "sql_check.csv"
AI_SQL = """
SELECT c.customer_id, c.customer_name, SUM(o.amount)
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
AND o.order_date >= '2026-08-01'
AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;
"""
REVISED_SQL = """
SELECT c.customer_id, c.customer_name, COALESCE(SUM(o.amount), 0)
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.customer_id
AND o.status = 'PAID'
AND o.order_date >= '2026-08-01'
AND o.order_date < '2026-09-01'
GROUP BY c.customer_id, c.customer_name
ORDER BY c.customer_id;
"""
EXPECTED = [
(1, "Ana", 100),
(2, "Ben", 25),
(3, "Cara", 0),
]
def run_query(connection, sql):
return connection.execute(sql).fetchall()
def main() -> None:
if not DATABASE.is_file():
raise FileNotFoundError(f"Database not found: {DATABASE}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
connection = sqlite3.connect(f"file:{DATABASE.resolve()}?mode=ro", uri=True)
try:
ai_rows = run_query(connection, AI_SQL)
revised_rows = run_query(connection, REVISED_SQL)
finally:
connection.close()
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
with REPORT.open("x", encoding="utf-8", newline="") as stream:
writer = csv.writer(stream)
writer.writerow(["query", "rows", "matches_expected"])
writer.writerow(["AI", repr(ai_rows), ai_rows == EXPECTED])
writer.writerow(["REVISED", repr(revised_rows), revised_rows == EXPECTED])
print(f"Expected rows: {len(EXPECTED)}.")
print(f"AI rows: {len(ai_rows)}; matches expected: {ai_rows == EXPECTED}.")
print(
f"Revised rows: {len(revised_rows)}; "
f"matches expected: {revised_rows == EXPECTED}."
)
print(f"Report: {REPORT.as_posix()}")
if revised_rows != EXPECTED:
raise RuntimeError("Revised SQL does not match the expected result.")
if __name__ == "__main__":
main()
python ai_sql_check.pyAIクエリはAnaとBenだけを返すため2行となり、期待結果とは一致しないはずです。修正版クエリは3人すべての顧客を返し、手計算した表と完全に一致するはずです。
| クエリ | 返される行数 | 期待される総合計 | 期待表と一致 |
|---|---|---|---|
| AI | 2 | 125 | いいえ |
| 修正版 | 3 | 125 | はい |
AIの結果は、Caraに必要な0の行が欠けていて誤っているにもかかわらず、総合計125だけは正しいままになる可能性があります。これが、合計だけを確認するのでは不十分な理由です。
Expected rows: 3.
AI rows: 2; matches expected: False.
Revised rows: 3; matches expected: True.
Report: outputs/ai_sql_check_result/sql_check.csv| よくある問題 | 確認する点 |
|---|---|
| 予想外に行が欠ける | JOINの種類と、WHEREのフィルタがLEFT JOINの不一致行を削除していないか確認します。 |
| 合計が大きすぎる | 集計前に1対多のJOINで行が重複していないか確認します。 |
| 日付範囲が間違っている | 保存されている日付形式に合った明示的な下限と排他的な上限を使います。 |
| 0ではなくNULLになる | 一致がない場合にNULLを残すのか、COALESCEで0にするのか決めます。 |
| 総合計は正しいが詳細が間違っている | 総合計だけでなく、期待される各行を完全に比較します。 |
このテストで分かるのは、修正版クエリがこの小さな合成例と一致することだけです。実際のスキーマには、重複関係、NULL値、timestamps、time zones、refunds、追加の業務ルールが含まれる場合があります。そうした条件が重要なら、テストデータにも追加してください。
2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, pathlib, sqlite3 · 未実行
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。