本文详细代码请见:
http://www.blogjava.net/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
小结
从这个例子中我们体会了分治的思想:分治是把一个大问题分解成小问题,然后分别解决小问题,再组合起来大问题的解决方法就差不多了.这种思想在工程领域解决问题时很普遍,我们要学会使用这种思想来看待,分析和解决问题,不要贪多求大,结果导致在大问题面前一筹莫展.
其次我们可以从这个例子中学习找规律,然后借助规律的过程,现实世界千变万化,但都有规律可循,只要我们找到了规律,就等于找到了事物之门的钥匙.
接下了我们复习了正则表达式用于查找的方法,以前的正则表达式学习多用于验证匹配,其实这只是正则表达式的一部分功能.
最后从解析条件成单行的过程中,我们可以学习到一种解决问题的技巧,即当现实中的规律存在变数时加入人为设置的规律,这有时能使我们更好更快的解决问题.