sql-query-generation について
自然言語の質問を、提示されたデータベーススキーマに合わせたSQLへ変換するスキルです。分析用の構文に対応し、EXPLAINプランから性能上の問題を調べて改善案を示します。
主な機能
- 01自然言語の要件解析
- 02スキーマに沿ったクエリ構築
- 03JOIN、CTE、ウィンドウ関数
- 04EXPLAINによる性能分析
- 05クエリの平易な説明
活用シーン
- 01質問から分析クエリを生成
- 02遅いクエリの実行プラン確認
- 03期間別のデータ比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ seb1n/ awesome-ai-agent-skills --skill sql-query-generationインストール後、Claude Code で/sql-query-generationを入力して読み込まれたか確認します。
詳細
SQL クエリ生成
このスキルを使うと、AI エージェントは自然言語の質問を、正確で効率的な SQL クエリに変換できます。エージェントは対象のデータベーススキーマに従い、ユーザーの意図を適切なクエリ構文(結合、集計、ウィンドウ関数、CTE、サブクエリ)に対応付けます。また、EXPLAIN プランを使ってクエリのパフォーマンスを分析し、インデックス作成、述語のプッシュダウン、クエリの再構成などの最適化を推奨します。
ワークフロー
-
自然言語のリクエストを解析します。 分析の意図を抽出します。求められている指標、関係するエンティティ、適用するフィルター、結果の並べ替えやグループ化の方法を特定します。集計結果の要約を求めているのか、行単位の詳細を求めているのかを区別します。
-
データベーススキーマに対応付けます。 スキーマから関連するテーブルと列を特定します。曖昧な参照(たとえば、「sales」が
ordersテーブルまたはrevenue列を指す場合)を解決します。結果セットが不必要に膨らまないよう、外部キーの関係を使ってテーブル間の結合経路を判断します。 -
適切なクエリ構文を選択します。 複雑さに応じて、単純な集計、ウィンドウ関数、CTE、サブクエリから選択します。複数段階の計算には、可読性を高めるためにCTEを使います。累積合計、順位付け、パーティション内の比較にはウィンドウ関数を使います。暗黙的なカンマ区切りの結合より、明示的なJOINを優先します。
-
SQLクエリを生成します。 一貫した書式(キーワードは大文字、識別子は小文字、テーブルには別名を付け、句をインデント)で、構文的に正しいSQLを記述します。複雑なロジックにはコメントを付けます。計算式には必ず列の別名を指定します。
-
検証して最適化します。 生成したクエリに対してEXPLAIN(またはEXPLAIN ANALYZE)を実行し、実行計画を確認します。全テーブルスキャン、大規模なテーブルでのハッシュ結合、インデックスのない列に対するソート処理を確認します。推定コストが高い場合は、インデックスやクエリの書き換えを推奨します。
-
説明を添えて結果を返します。 クエリとともに、その処理内容、想定される出力形式、スキーマやデータについて置いた前提を、平易な言葉で説明します。
対応テクノロジー
- PostgreSQL — CTE、ウィンドウ関数、LATERAL結合、EXPLAIN ANALYZE
- MySQL — 共通テーブル式(8.0以降)、ウィンドウ関数(8.0以降)、EXPLAIN FORMAT=JSON
- SQLite — 軽量なクエリ、ウィンドウ関数(3.25以降)
- SQL Server — T-SQL拡張、CROSS APPLY、クエリストア分析
- BigQuery — STRUCT/ARRAY型、QUALIFY句、スロットベースのコスト見積もり
使い方
エージェントに自然言語の質問とデータベーススキーマ(DDL、スキーマ図、またはテーブルと列の一覧)を渡します。必要に応じて、対象のSQL方言、パフォーマンス上の制約、EXPLAIN分析を含めるかどうかも指定します。
例
例 1:自然言語から複雑な分析クエリを作成する
リクエスト:「過去12か月の総支出額が多い上位5人の顧客と、最も頻繁に注文した商品カテゴリ、およびその支出額を直前の12か月間と比較した結果を表示してください。」
WITH current_period AS (
SELECT
c.customer_id,
c.customer_name,
SUM(oi.quantity * oi.unit_price) AS total_spent,
COUNT(DISTINCT o.order_id) AS order_count
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date >= CURRENT_DATE - INTERVAL '12 months'
GROUP BY c.customer_id, c.customer_name
),
previous_period AS (
SELECT
c.customer_id,
SUM(oi.quantity * oi.unit_price) AS prev_total_spent
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date >= CURRENT_DATE - INTERVAL '24 months'
AND o.order_date < CURRENT_DATE - INTERVAL '12 months'
GROUP BY c.customer_id
),
top_categories AS (
SELECT DISTINCT ON (c.customer_id)
c.customer_id,
pc.category_name AS favorite_category,
COUNT(*) AS category_orders
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN product_categories pc ON p.category_id = pc.category_id
WHERE o.order_date >= CURRENT_DATE - INTERVAL '12 months'
GROUP BY c.customer_id, pc.category_name
ORDER BY c.customer_id, COUNT(*) DESC
)
SELECT
cp.customer_name,
cp.total_spent,
cp.order_count,
tc.favorite_category,
pp.prev_total_spent,
ROUND(
(cp.total_spent - COALESCE(pp.prev_total_spent, 0))
/ NULLIF(pp.prev_total_spent, 0) * 100, 1
) AS spending_change_pct
FROM current_period cp
LEFT JOIN previous_period pp ON cp.customer_id = pp.customer_id
LEFT JOIN top_categories tc ON cp.customer_id = tc.customer_id
ORDER BY cp.total_spent DESC
LIMIT 5;
-- Expected output:
-- customer_name | total_spent | order_count | favorite_category | prev_total_spent | spending_change_pct
-- Acme Corp | 284,500.00 | 47 | Electronics | 198,200.00 | 43.5
-- GlobalTech | 231,800.00 | 38 | Software | 245,100.00 | -5.4
-- ...
例 2:EXPLAIN分析で遅いクエリを最適化する
元の遅いクエリ(500万行で12.4秒かかります):
SELECT product_name, SUM(quantity * unit_price) AS revenue
FROM order_items oi, products p, orders o
WHERE oi.product_id = p.product_id
AND oi.order_id = o.order_id
AND o.order_date BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY product_name
ORDER BY revenue DESC;
EXPLAIN ANALYZEの出力(問題を示す指標):
Seq Scan on orders o (cost=0.00..98456.00 rows=1245000)
Filter: (order_date >= '2024-01-01' AND order_date <= '2024-12-31')
Rows Removed by Filter: 3755000
Hash Join (cost=98456.00..245678.00 rows=3200000)
Sort (cost=312456.00..312460.00 rows=8500)
Sort Method: external merge Disk: 4096kB
特定された問題:
ordersのシーケンシャルスキャン —order_dateにインデックスがありません- 暗黙的な結合構文により、オプティマイザーから結合順序が分かりません
work_memが不足しているため、ソートがディスクにスピルしています
最適化後のクエリ:
-- Step 1: Create index (one-time)
CREATE INDEX idx_orders_date ON orders (order_date)
INCLUDE (order_id);
-- Step 2: Rewrite with explicit joins and date index hint
SELECT
p.product_name,
SUM(oi.quantity * oi.unit_price) AS revenue
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY p.product_name
ORDER BY revenue DESC;
-- After optimization: 0.34 seconds (36x faster)
-- EXPLAIN now shows:
-- Index Scan on idx_orders_date (rows=1245000, actual=1243892)
-- Merge Join (cost reduced by 85%)
-- Sort Method: quicksort Memory: 512kB
ベストプラクティス
- 意図を明確にし、意図しないクロス結合を防ぐため、カンマ区切りの暗黙的な結合ではなく、常に明示的な JOIN 構文を使用してください。
- 読みやすさを高め、複雑なクエリでの曖昧さを避けるため、すべてのテーブルと計算列に別名を付けてください。
- 複雑なクエリは、サブクエリを深くネストするのではなく、CTE を使って名前付きの論理的なステップに分割してください。
- 早い段階でフィルターを適用してください。結合によって行数が増える前にデータセットを絞り込めるよう、駆動テーブルに WHERE 条件を指定してください。
- 1 対多の関係によるカウントの水増しを避けるため、結合したテーブルでは
COUNT(*)よりCOUNT(DISTINCT col)を優先してください。 - 生成したクエリを最終版として提示する前に、必ず実際のスキーマに対してテストしてください。自然言語の説明にある列名や型は、実際の DDL と異なることがよくあります。
エッジケース
- 曖昧な列名。 複数のテーブルに同じ名前の列(例:
id、name、status)がある場合は、必ずテーブルの別名を使って修飾してください。自然言語によるリクエストに本当に曖昧さがある場合は、ユーザーに確認してください。 - 集計での NULL の扱い。
SUM、AVG、COUNT(col)は NULL を暗黙的に無視します。NULL に意味がある場合(例:「売上なし」)は、集計前にCOALESCE(col, 0)を使い、その前提を明記してください。 - 計算指標でのゼロ除算。 分母を
NULLIF(denominator, 0)で囲み、エラーの代わりに NULL を返すようにしてから、プレゼンテーション層で NULL を処理してください。 - 日付とタイムゾーンの不一致。 タイムスタンプ列を日付でフィルターする場合は、境界を明確にしてください。終端境界の午前 0 時も含まれる
BETWEENではなく、>= '2024-01-01' AND < '2025-01-01'を使用してください。 - 非常に大きな結果セット。 探索的なクエリには必ず
LIMITを指定してください。本番クエリでは、深いページでのパフォーマンスを高めるため、OFFSET/FETCHまたはキーセットページネーションでページネーションを追加してください。