SQL文の種類とCRUD対応表
| SQL文 | CRUD | COBOLでの登場頻度 | 注意点 |
|---|---|---|---|
SELECT | R | 非常に多い | JOIN・サブクエリで複数テーブル |
INSERT INTO | C | 多い | 基本的にシンプル |
UPDATE | U | 多い | サブクエリの場合は複数テーブル |
DELETE FROM | D | 中程度 | サブクエリの場合は複数テーブル |
MERGE INTO | CU | 少ない(DB2等) | 条件によってCもUも発生 |
DECLARE CURSOR | R | 多い(カーソル処理) | SELECTとして扱う |
正規表現によるCRUD分類(シンプル版)
JSqlParserが使えない場合やフォールバック用の軽量実装です。
import re
from dataclasses import dataclass
@dataclass
class CrudEntry:
table_name: str
operations: set # {'C', 'R', 'U', 'D'} の部分集合
# CRUD 操作を判別するパターン
_CRUD_PATTERNS = [
('C', re.compile(r'^\s*INSERT\s+INTO\s+([A-Z][A-Z0-9_-]*)', re.IGNORECASE)),
('R', re.compile(r'^\s*SELECT\b.*?\bFROM\s+([A-Z][A-Z0-9_-]*)', re.IGNORECASE | re.DOTALL)),
('R', re.compile(r'^\s*DECLARE\s+\w+\s+CURSOR\b.*?\bFROM\s+([A-Z][A-Z0-9_-]*)', re.IGNORECASE | re.DOTALL)),
('U', re.compile(r'^\s*UPDATE\s+([A-Z][A-Z0-9_-]*)', re.IGNORECASE)),
('D', re.compile(r'^\s*DELETE\s+FROM\s+([A-Z][A-Z0-9_-]*)', re.IGNORECASE)),
]
def classify_crud_regex(sql: str) -> list[CrudEntry]:
"""正規表現でSQL文のCRUD種別とテーブル名を判別(フォールバック用)"""
results = {}
for op, pattern in _CRUD_PATTERNS:
m = pattern.match(sql)
if m:
table = m.group(1).upper()
if table not in results:
results[table] = CrudEntry(table_name=table, operations=set())
results[table].operations.add(op)
return list(results.values())
JSqlParserによるCRUD分類(高精度版)
JSqlParserはJavaライブラリですが、Pythonからsubprocessを使って呼び出すか、
JPype を経由して JVM を起動する方法があります。ここでは薄いJavaラッパーを介する方法を採ります。
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.*;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.util.TablesNamesFinder;
import java.util.*;
/**
* 標準入力から SQL を読み込み、テーブル名と操作種別を
* "TABLE_NAME:OPERATION" 形式で標準出力に書き出すラッパー。
* Python から subprocess で呼び出して使う。
*/
public class CrudAnalyzer {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()) {
String sql = sc.nextLine().trim();
if (sql.isEmpty()) continue;
try {
Statement stmt = CCJSqlParserUtil.parse(sql);
TablesNamesFinder finder = new TablesNamesFinder();
List<String> tables = finder.getTableList(stmt);
String op = detectOperation(stmt);
for (String t : tables) {
System.out.println(t.toUpperCase() + ":" + op);
}
} catch (Exception e) {
System.err.println("PARSE_ERROR: " + sql.substring(0, Math.min(60, sql.length())));
}
}
}
private static String detectOperation(Statement stmt) {
if (stmt instanceof Select) return "R";
if (stmt instanceof Insert) return "C";
if (stmt instanceof Update) return "U";
if (stmt instanceof Delete) return "D";
return "?";
}
}
import subprocess
def classify_crud_jsqlparser(sql_list: list[str], jar_path: str) -> dict[str, list[CrudEntry]]:
"""
JSqlParser ラッパーJARを呼び出してSQL群を一括解析する。
戻り値: {sql文字列: [CrudEntry, ...]} の辞書
"""
proc = subprocess.run(
['java', '-cp', jar_path, 'CrudAnalyzer'],
input='\n'.join(sql_list),
capture_output=True,
text=True,
encoding='utf-8'
)
results = {sql: {} for sql in sql_list}
lines = proc.stdout.strip().splitlines()
idx = 0
for sql in sql_list:
entries = {}
# 各SQL の出力行数はテーブル数に依存するため、
# PARSE_ERROR が出るまで or 次のSQLまで読む(簡易実装)
while idx < len(lines) and not lines[idx].startswith('PARSE_ERROR'):
parts = lines[idx].split(':')
if len(parts) == 2:
table, op = parts[0].strip(), parts[1].strip()
if table not in entries:
entries[table] = CrudEntry(table_name=table, operations=set())
entries[table].operations.add(op)
idx += 1
results[sql] = list(entries.values())
return results
MERGE文・UPSERT の扱い
⚠️ MERGE文(DB2・Oracle)
DB2 や Oracle では MERGE INTO 文が使われます。この文は WHEN MATCHED THEN UPDATE(U)と WHEN NOT MATCHED THEN INSERT(C)の両方を含むため、同一テーブルに C と U の両方を記録する必要があります。JSqlParser の Merge クラスで判別できます。
正規表現とJSqlParserの組み合わせ
def classify_crud(sql: str, jar_path: str | None = None) -> list[CrudEntry]:
"""
JSqlParser(利用可能な場合)で解析し、失敗時は正規表現でフォールバック。
"""
if jar_path:
try:
result = classify_crud_jsqlparser([sql], jar_path)
entries = result.get(sql, [])
if entries:
return entries
except Exception:
pass
# フォールバック
return classify_crud_regex(sql)
分類結果の構造
分類結果の例
SQL: SELECT TOKUI-CD, TOKUI-NM FROM TOKUI WHERE TOKUI-CD = ?
→ CrudEntry(table_name='TOKUI', operations={'R'})
SQL: SELECT U.URIAGE-NO, M.SHOHIN-CD FROM URIAGE U JOIN MEISAI M ON ...
→ CrudEntry(table_name='URIAGE', operations={'R'})
→ CrudEntry(table_name='MEISAI', operations={'R'})
SQL: INSERT INTO URIAGE (URIAGE-NO, TOKUI-CD, KIN-GAK) VALUES (?, ?, ?)
→ CrudEntry(table_name='URIAGE', operations={'C'})
✅ 次の章では…
PART 05 では PART 03・04 の解析結果をプロジェクト全体で集約し、プログラム × テーブル → CRUD 操作セット という行列データ構造を完成させます。