本文詳細代碼請見:
http://www.tkk7.com/sitinspring/archive/2008/03/14/186372.html
問題:將左邊的SQL語句解析成右邊的形式
Select c1,c2,c3 From t1,t2,t3 Where condi1=5 and condi6=6 or condi7=7 Group by g1,g2,g3 order by g2,g3
select
c1,
c2,
c3
from
t1,
t2,
t3
where
condi1=5 and
condi6=6 or
condi7=7
group by
g1,
g2,
g3
order by
g2,
g3
按關鍵字找出SQL語句中各部分
我們閱讀SQL語句會把整句分來成列,表,條件,分組字段,排序字段來理解,解析SQL的目的也是這樣.
分解SQL語句有規律可循,以列為例,它必定包含在select和from之間,我們只要能找到SQL語句中的關鍵字select和from,就能找到查詢的列.
怎么找到select和from之間的文字呢?其實一個正則表達式就能解決:(select)(.+)(from),其中第二組(.+)代表的文字就是select和from之間的文字.
程序見右邊.
/**
* 從文本text中找到regex首次匹配的字符串,不區分大小寫
* @param regex: 正則表達式
* @param text:欲查找的字符串
* @return regex首次匹配的字符串,如未匹配返回空
*/
private static String getMatchedString(String regex,String text){
Pattern pattern=Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
Matcher matcher=pattern.matcher(text);
while(matcher.find()){
return matcher.group(2);
}
return null;
}
解析函數分析
private static String getMatchedString(String regex,String text){
Pattern pattern=Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
Matcher matcher=pattern.matcher(text);
while(matcher.find()){
return matcher.group(2);
}
return null;
}
左邊的這個函數,第一個參數是擬定的正則表達式,第二個是整個SQL語句.
當正則表達式為(select)(.+)(from)時,程序將在SQL中查找第一次匹配的地方(有Pattern.CASE_INSENSITIVE的設置,查找不區分大小寫),如果找到了則返回模式中的第二組代表的文字.
如果sql是select a,b from tc,則返回的文字是a,b.
選擇的表對應的查找正則表達式
選擇的表比較特殊,它不想選擇的列一樣固定處于select和from之間,當沒有查找條件存在時,它處于from和結束之間;當有查找條件存在時,它處于from和where之間.
因此查詢函數寫為右邊的形式:
/**
* 解析選擇的表
*
*/
private void parseTables(){
String regex="";
if(isContains(sql,"\\s+where\\s+")){
regex="(from)(.+)(where)";
}
else{
regex="(from)(.+)($)";
}
tables=getMatchedString(regex,sql);
}
isContains函數
isContains函數用于在lineText中查找word,其中不區分大小些,只要找到了即返回真.
/**
* 看word是否在lineText中存在,支持正則表達式
* @param lineText
* @param word
* @return
*/
private static boolean isContains(String lineText,String word){
Pattern pattern=Pattern.compile(word,Pattern.CASE_INSENSITIVE);
Matcher matcher=pattern.matcher(lineText);
return matcher.find();
}
解析查找條件的函數
private void parseConditions(){
String regex="";
if(isContains(sql,"\\s+where\\s+")){
// 包括Where,有條件
if(isContains(sql,"group\\s+by")){
// 條件在where和group by之間
regex="(where)(.+)(group\\s+by)";
}
else if(isContains(sql,"order\\s+by")){
// 條件在where和order by之間
regex="(where)(.+)(order\\s+by)";
}
else{
// 條件在where到字符串末尾
regex="(where)(.+)($)";
}
}
else{
// 不包括where則條件無從談起,返回即可
return;
}
conditions=getMatchedString(regex,sql);
}
解析GroupBy的字段
private void parseGroupCols(){
String regex="";
if(isContains(sql,"group\\s+by")){
// 包括GroupBy,有分組字段
if(isContains(sql,"order\\s+by")){
// group by 后有order by
regex="(group\\s+by)(.+)(order\\s+by)";
}
else{
// group by 后無order by
regex="(group\\s+by)(.+)($)";
}
}
else{
// 不包括GroupBy則分組字段無從談起,返回即可
return;
}
groupCols=getMatchedString(regex,sql);
}
解析OrderBy的字段
private void parseOrderCols(){
String regex="";
if(isContains(sql,"order\\s+by")){
// 包括order by,有分組字段
regex="(order\\s+by)(.+)($)";
}
else{
// 不包括GroupBy則分組字段無從談起,返回即可
return;
}
orderCols=getMatchedString(regex,sql);
}
得到解析后的各部分
按以上解析方法獲得了列,表,條件,分組條件,排序條件各部分之后,它們會存儲到各個成員變量中.
注意這些成員變量的原值都是null,如果在SQL語句中能夠找到對應的部分的話它們將借助getMatchedString獲得值,否則還是null.我們通過判斷這些成員變量是否為空就能知道它對應的部分是否被解析出來.
/**
* 待解析的SQL語句
*/
private String sql;
/**
* SQL中選擇的列
*/
private String cols;
/**
* SQL中查找的表
*/
private String tables;
/**
* 查找條件
*/
private String conditions;
/**
* Group By的字段
*/
private String groupCols;
/**
* Order by的字段
*/
private String orderCols;
取得不需要單行顯示時的SQL語句
進展到這一步,SQL語句中列,表,條件,分組條件,排序條件各部分都被獲取了出來,這時把它們重新組合一下就能得到整理后的SQL語句.
如下面的SQL語句將變成右邊的部分(先使靜態成員isSingleLine=false):
Select c1,c2,c3 From t1,t2,t3 Where condi1=5 and condi6=6 or condi7=7 Group by g1,g2,g3 order by g2,g3
select
c1,c2,c3
from
t1,t2,t3
where
condi1=5 and condi6=6 or condi7=7
group by
g1,g2,g3
order by
g2,g3
進一步解析
有時我們需要把列,表,條件,分組條件,排序條件單行顯示以方便查看或加上注釋,這就要求我們對列,表,條件,分組條件,排序條件等進行進一步解析.
初看解析很方便,以固定的分隔符劈分即可,但需要注意的是查詢條件中分隔符有and和or兩種,如果貿然分隔會使重新組合時使SQL失真.
推薦一種做法,我們可以在分隔符后加上一個標志如空行,然后再以這個標志來劈分.這樣就不會使SQL失真了.
請見下頁的getSplitedParagraph函數.
getSplitedParagraph函數
private static List<String> getSplitedParagraph(String paragraph,String splitStr){
List<String> ls=new ArrayList<String>();
// 先在分隔符后加空格
Pattern p = Pattern.compile(splitStr,Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(paragraph);
StringBuffer sb = new StringBuffer();
boolean result = m.find();
while (result) {
m.appendReplacement(sb, m.group(0) + Crlf);
result = m.find();
}
m.appendTail(sb);
// 再按空格斷行
String[] arr=sb.toString().split("[\n]+");
for(String temp:arr){
ls.add(FourSpace+temp+Crlf);
}
return ls;
}
處理結果
把靜態成員變量isSingleLine=true后我們來看看執行結果:
select
c1,
c2,
c3
from
t1,
t2,
t3
where
condi1=5 and
condi6=6 or
condi7=7
group by
g1,
g2,
g3
order by
g2,
g3
小結
從這個例子中我們體會了分治的思想:分治是把一個大問題分解成小問題,然后分別解決小問題,再組合起來大問題的解決方法就差不多了.這種思想在工程領域解決問題時很普遍,我們要學會使用這種思想來看待,分析和解決問題,不要貪多求大,結果導致在大問題面前一籌莫展.
其次我們可以從這個例子中學習找規律,然后借助規律的過程,現實世界千變萬化,但都有規律可循,只要我們找到了規律,就等于找到了事物之門的鑰匙.
接下了我們復習了正則表達式用于查找的方法,以前的正則表達式學習多用于驗證匹配,其實這只是正則表達式的一部分功能.
最后從解析條件成單行的過程中,我們可以學習到一種解決問題的技巧,即當現實中的規律存在變數時加入人為設置的規律,這有時能使我們更好更快的解決問題.