
レガシーコード500本からSQLを自動抽出できるのか試してみた 〜Claude Codeのヘッドレス実行とHaiku/Sonnet/Opusの比較〜
概要
こんにちは、クラスメソッド製造ビジネステクノロジー部の田中聖也です。
SQL Server のバージョンアップ(2008 R2以前 → 2019/2022)のような話になると、必ず「アプリ側から実行されているSQLを全部洗い出す」という影響調査が必要になってきます。
対象が数千ファイル規模のレガシーコードだと、途方もない移行作業になりますよね・・・
Gemini, Copilot, CluadeなどのAIチャットを活用して「ソースをチャットに貼り付けて抽出してもらう」なんですが、何千ファイルに対してするのは現実的ではないと感じました。
そこで、CLIのヘッドレス実行(対話画面を使わず、コマンドから直接LLMを呼び出して結果だけ受け取る実行方式)でバッチ化できないかを検証してみました。

改めて今回の検証の目的です↓
- レガシーコード(Java/VB.NET)からLLMでSQLを漏れなく抽出できるか
上記の内容を検証するために2つの検証方法でやってみました。
検証方法1
目的
目的1,2に対して1モデルで500ファイルを精度高く完走できるか
対象としたファイル数
- 500ファイル
- 1ファイルあたり1つのSQLが生成される
使用モデル
- Claude Haiku 4.5
検証方法2
目的
目的1,2に対して難易度の高い(複数メソッドに分散したSQL,入れ子分岐など)に実施した場合にモデルごとの性能比較を行う。
- 正答率
- コスト
- 所要時間
対象としたファイル数
- 100ファイル
- 1ファイルあたり4~8のSQLが生成される
- 複数メソッドに分散したSQL
- WHERE句だけ違う類似SQLを離して配置
- 不揃いな断片への分割
- 入れ子分岐
- 定数を別メソッドで連結
など
使用モデル
- Claude Haiku 4.5
- Claude Sonnet 5.0
- Claude Opus 5.0
検出したいのはこういうコード例
「SQLを抽出する」と一言で言っても、実際のレガシーコードでSQLがそのまま1行で書かれていることは意外と少ないです。
実際に検証で使ったサンプルが以下の4つです。
例1: 条件分岐でSQLが枝分かれする(Java)
public ResultSet find(boolean useDate) throws SQLException {
String sql = "SELECT ord_no, amt FROM T_ORDER WHERE ord_no = ?";
if (useDate) {
sql = sql + " AND upd_dt > ?";
}
return conn.prepareStatement(sql).executeQuery();
}
// decoy: natural language, not SQL
private String msg = "Failed to UPDATE the configuration file";
検出したいSQL↓
SELECT ord_no, amt FROM T_ORDER WHERE ord_no = ?
SELECT ord_no, amt FROM T_ORDER WHERE ord_no = ? AND upd_dt > ?
例2: StringBuilderで組み立てる(Java)
public ResultSet find() throws SQLException {
StringBuilder q = new StringBuilder();
q.append("SELECT denpyo_no FROM T");
q.append("_URIAGE WHERE upd_dt = ");
q.append("'2024-01-01 00:00:00.000'");
return conn.prepareStatement(q.toString()).executeQuery();
}
検出したいSQL↓
SELECT denpyo_no FROM T_URIAGE WHERE upd_dt = '2024-01-01 00:00:00.000'
例3: & 連結で組み立てる(VB.NET)
Public Function Find() As DataTable
Dim sql As String = "DELETE FROM T_NYUKIN"
sql = sql & " WHERE nyukin_no = ?"
Return Nothing
End Function
検出したいSQL↓
DELETE FROM T_NYUKIN WHERE nyukin_no = ?
例4: 行継続で単語の途中から割れる(VB.NET)
Public Function Find() As DataTable
Dim sql As String = "SELECT shohin_cd FROM m" & _
"aster.dbo.M_SHOHIN.tanka"
Return Nothing
End Function
検出したいSQL↓
SELECT shohin_cd FROM master.dbo.M_SHOHIN.tanka
検証データを作ってみた
正解データが無いと精度が測れないので、正解データ(どのファイルの何行目にどのSQLがあるかの答え)付きのサンプルを生成しました。
生成したサンプルの内訳↓
| 項目 | 結果 |
|---|---|
| ファイル数 | 500(Java 300 / VB.NET 200) |
| SQL文 | 440 |
| 文字コード | Shift_JIS 208 / UTF-8 292 |
| 改行コード | CRLF 262 / LF 238 |
| SQL無しファイル | 127(25%) ← 偽陽性測定用 |
| severity(影響度の区分) | ERROR 22 / WARNING 68 / INFO 60 |
埋め込んだ抽出パターンは8種類です。
branch: 134proc: 72literal: 71concat: 44dynamic: 34builder: 33continuation: 27concat_amp: 25
精度の指標
今回はRecallを選択しました。
理由としては移行時に取りこぼしが発生する = 影響調査の漏れになり、移行後の本番障害に繋がりかねないと考えたからです。
検証1: 500本をバッチ抽出してみた
LLMにファイル探索はさせず(--tools "")、外部スクリプトから claude -p をヘッドレス呼び出しします。
この500本検証は Claude Haiku 4.5 のみで実施しています。
やってみた
500本完走の実績がこちら↓
| 指標 | 結果 |
|---|---|
| 処理ファイル | 500/500 |
| 抽出SQL | 440(正解440と一致) |
| Recall | 98.9%(435/440) |
| 行番号の誤り | 0 |
| 偽陽性 | 0(SQL無し127本から誤検出なし) |
| ハルシネーション | 0 |
| エラー・切り詰め | 0 |
| コスト | $3.14($0.0063 per file) |
| 所要時間 | 102.1分(batch=5 / workers=4) |
Recallが100%になっていませんが、LLM側の問題ではなく精度評価側のソースコードに間違いがありました。
ここらへんも修正して再実行するとRecallが100%になりました。
どこが悪かったので、どう修正したのは自分の言葉で説明しきれない部分もあるので、ここは理解したあとに追記しようと思います。
検証2: 高難易度の100本でモデルごとの性能を比較する
検証1と検証2の検証ファイル差分
| 検証1 | 検証2 | |
|---|---|---|
| ファイル数 | 500 | 100 |
| SQL文 | 440 | 866 |
| SQLが2箇所以上のファイル | 0 | 83 |
| 1ファイルの場所数 | 1 | 4〜8 |
| 行数(中央値) | 26 | 185 |
| 1ファイル最大SQL数 | 2 | 20 |
やってみた
100本(866件)を、同一データ・同一プロンプト・同一スキーマ・同一バッチ設定で3モデル回しました。
以下が結果です↓
| 指標 | Haiku | Sonnet | Opus |
|---|---|---|---|
| Recall | 99.9% | 99.8% | 100% |
| 見逃し | 1 | 1 | 0 |
| 想定外の抽出 | 0 | 1 | 0 |
| 未照合 | 8 | 8 | 0 |
| 偽陽性 | 0 | 0 | 0 |
| コスト | $4.25 | $7.07 | $9.04 |
| 所要時間 | 21.0分 | 7.4分 | 6.7分 |
検証で分かったこと
- Opusのみが全項目クリーン
- 見逃し0・想定外0・未照合0を満たすのはopusだけでした
- 安い=速いではない
- 所要時間はHaikuが最遅の21.0分でした。今までは早いが性能が悪いと思っていたのですが、意外な結果でした。ここはもうちょっと深堀たいと思います。
- 偽陽性は全モデル0
- SQL無し17本から誤検出なし。デコイ英文には釣られませんでした
まとめ
LLMでどこまでレガシーシステムのマイグレーションを支援できるかを検証しました。
検証前は一番性能の高いモデルじゃないと無理なんだろうなと思っていましたが、そうでもありませんでした。
ただ、実務レベルになるともっと複雑なロジックが組まれていると思うので、本当に大丈夫なのかな?という不安も残るような検証でした。








