數據是程式處理的核心內容,通常存儲於關係型資料庫中,操作數據最終都需透過 SQL 語句完成。因此,解讀、分析與處理 SQL 語句成為程式開發人員的基本功之一。然而,這項任務往往枯燥乏味。若能讓電腦自動完成基礎分析——如識別 SQL 涉及的表、欄位與條件——便能釋放開發者精力,專注於更具挑戰性的工作。本文將探討如何解析單句 SQL 語句,提出一套完整解決方案,拋磚引玉,期待同好指正。
此處所謂「單句 SQL」指不包含巢狀結構的 SQL 語句,涵蓋 SELECT、INSERT、DELETE、UPDATE 四大類型(另有 INSERT ... SELECT 子類)。其中 SELECT 最為複雜,下文將以此為例說明。關於巢狀 SQL、特別是多重巢狀的分析較為棘手,筆者暫無成熟方案,望不吝賜教。
正式分析前,預處理能大幅減輕後續編碼負擔。主要工作包括:
ENDOFSQL(用意稍後說明)sql = sql.trim();
sql = sql.toLowerCase();
sql = sql.replaceAll("\\s+", " ");
sql = sql + " ENDOFSQL";
經預處理後,多行、大小寫混雜的 SQL 變為單行小寫字串。以 SELECT 為例,典型結構包含 SELECT、FROM、WHERE、GROUP BY、ORDER BY 等子句。若能準確切分這些子句,分析工作即前進一大步。
觀察範例:
select c1,c2,c3 from t1,t2 where condi3=3 or condi4=5 order by o1,o2
可發現各子句的起止標誌:
SELECT 子句:起始 select,結束 fromFROM 子句:起始 from,結束 where/on/having/group by/order by/ENDOFSQLWHERE 子句:起始 where,結束 order byORDER BY 子句:起始 order by,結束 ENDOFSQL據此,利用正則表達式即可提取各子句。以 FROM 子句為例:
"(from)(.+)( where | on | having | group\\s+by | order\\s+by | ENDOFSQL)"
該正則涵蓋 FROM 後接各種可能的下一子句或結束標記,確保完整捕獲。
直接對完整 SQL 套用上述正則會因「貪婪匹配」導致錯誤。例如:
select ... from ... where ... order by ...
正則會匹配到 from ... where ... order by 而非預期的 from ... where。解決策略:從 SQL 開頭開始,逐步增加截取長度進行匹配,一旦找到首個符合條件的片段即停止。雖犧牲部分效率,但換取正確結果,利大於弊。
Pattern pattern = Pattern.compile(segmentRegExp, Pattern.CASE_INSENSITIVE);
for (int i = 0; i <= sql.length(); i++) {
String shortSql = sql.substring(0, i);
Matcher matcher = pattern.matcher(shortSql);
while (matcher.find()) {
start = matcher.group(1);
body = matcher.group(2);
end = matcher.group(3);
parseBody();
return;
}
}
取得子句主體(body)後,需按特定分隔符拆分。以 FROM 子句為例,分隔符包含逗號、LEFT JOIN、RIGHT JOIN、INNER JOIN 等,正則為:
"(,\\s*|\\s+left\\s+join\\s+|\\s+right\\s+join\\s+|\\s+inner\\s+join\\s+)"
大多數子句主體僅以逗號分隔,正則相對簡單。拆分時保留分隔符以便後續還原,採用 Matcher.appendReplacement 將分隔符替換為「分隔符+換行」,再按換行切割存入列表。
分析完成後,將各子句按順序重組,並對主體片段逐行縮排,輸出清晰可讀的 SQL。例如:
select
c1,
c2,
c3
from
t1,
t2,
t3
where
condi1=5 and
condi6=6 or
condi7=7
order by
g1,
g2
方案採用工廠模式與策略模式,核心類別包括:
SqlParserUtil:入口工具類,負責預處理與分發SingleSqlParserFactory:根據 SQL 關鍵字判斷類型,實例化對應解析器BaseSingleSqlParser:抽象基類,定義解析流程模板SelectSqlParser、InsertSqlParser、InsertSelectSqlParser、UpdateSqlParser、DeleteSqlParserSqlSegment:封裝單一子句的正則、分隔符、解析邏輯與格式化輸出各解析器在 initializeSegments() 中定義該類型 SQL 的子句正則與主體分隔符,實現高度可擴展的結構化解析。
解析效果演示:https://www.blogjava.net/heyang/archive/2009/02/03/253026.html