數據是程序處理的主要內容,它一般存儲在關系型數據庫中,要操作它們最終必須要通過SQL語句來完成,因此,解讀分析和處理SQL語句成為程序員的基本工作內容之一,當然有時這項任務是比較乏味的,如果讓計算機來完成一些基本的分析解讀工作如找出SQL語句涉及了哪些表,字段和條件等,可以幫助程序員解放出部分精力,投入到更有挑戰性和復雜性的任務中去,本文將就如何解析單句SQL語句提出自己的解決方案和大家探討,希望大家不吝批評指正。
首先說明以下單句SQL的范疇,它是指不存在嵌套的SQL語句,包括Select,insert,delete,update四大類型(具體的解析子類還有一種insert select類型),其中以select最為復雜,下面將以它為例。另關於嵌套SQL尤其是多重嵌套SQL的分析似乎比較復雜,我一時沒想出好的解決方案,如果您知道請不吝賜教。
1.關於SQL語句的預處理。
在對sql語句進行分析之前,有必要對它進行一些預處理,這樣能減輕不少後面編程的負擔。
預處理的主要工作是消除SQL語句前後的空白,將其中的連續空白字符(包括空格,TAB和回車換行)替換成單個空格;將sql語句全變成小寫形式(或大寫形式);在SQL語句的尾後加上結束符號,至於為什麼加,這裡先買個關子。具體的語句如下:
sql=sql.trim();
sql=sql.toLowerCase();
sql=sql.replaceAll("s+", " ");
sql=""+sql+" ENDOFSQL";
2.將SQL語句分離成片段。
經過第一步的工作,一個多行的,存在大小寫混雜的SQL語句已經變成了單行的小寫SQL語句,接下來我們需要把整句分離成更小的片段。以Select語句為例,其中可能存在有select子句部分,from子句部分,where子句部分,group by子句和order by子句等,如果能成功的把整句分離成這些子句,我們的分析工作又前進了一步。先讓我們看看下面的SQL示例:
select c1,c2,c3 from t1,t2 where condi3=3 or condi4=5 order by o1,o2
通過觀察我們可以發現,select子句是select c1,c2,c3 from,它的起始標志是select,結束標志是from;from子句是from t1,t2 where,它的起始標志是from,結束標志是where;where子句是where condi3=3 or condi4=5,它的起始標志是where,結束標志是order by;order by子句是order by o1,o2其起始標志是order by,剛才我們在整句SQL尾後加上了" ENDOFSQL"字樣,因此,order by子句的結束標志是" ENDOFSQL"。
這個分析給我們解析SQL語句提供了一個思路,如果我們能找到各個子句的前後標志,在正則表達式的幫助下我們就可以輕松的獲得每一種子句,下面給出一個找到from子句的完整正則表達式:
"(from)(.+)( where | on | having | groups+by | orders+by | ENDOFSQL)"
這句正則表示式讓程序到整句SQL中查找符合這樣條件的文本單元:它以from開頭,結束標志是where,on,having,group by,order by或語句結束中間的一個,開始標志和結束標志之間可以是任何字符。這樣,from子句的各種情況就都囊括進這個正則表達式了,它能找到以下類型的各種form子句:
from .... where
from .... on
from .... having
from .... group by
from .... order by
from .... ENDOFSQL(這個ENDOFSQL是預處理時加上的,如果用$符號會給程序造成麻煩)
3.找到片段中的各個部分。
有了表示片段的正則表達式,找到片段後從中分離出片段起始標志start,片段主體body和片段結束標志end就很容易了,請見代碼:
Pattern pattern=Pattern.compile(segmentRegExp,Pattern.CASE_INSENSITIVE);
for(int i=0;i<=sql.length();i++){
String shortSql=sql.substring(0, i);
//System.out.println(shortSql);
Matcher matcher=pattern.matcher(shortSql);
while(matcher.find()){
start=matcher.group(1);// 片段起始標志start
body=matcher.group(2);// 片段主體body
end=matcher.group(3);// 片段結束標志end
parseBody();
return;
}
}
這段代碼為什麼要逐漸從SQL開頭開始截取不斷增長的SQL語句進行分析而不是直接對整個SQL進行查找呢?原因是表示子句的正則表達式比較貪婪,它會竭力向後尋找,比如說SQL語句是這樣寫的:
select .... from .... where .... order by ....
那麼用"(from)(.+)( where | on | having | groups+by | orders+by | ENDOFSQL)"進行查找得到from子句不是
from .... where
而是
from .... where .... order by
這當然不是我們想要的結果,因此采取了從SQL開頭開始截取不斷增長的SQL語句進行分析,找到了from .... where部分就不用繼續往下找了,當然這在效率上有降低,但一些效率的付出相對於正確的結果來說是值得的。
4.將片段主體部分劈分開來
還是拿from子句做例子,得到它以後我們希望繼續進行分析,最終得到from子句的表,這部分工作比較簡單,使用特定的標志對body進行查找劈分即可,from子句的劈分標志較多,用正則表達式寫出來是“(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)”,各種情況都要涉及到。其實大多數子句主體部分的劈分標志都是逗號,用正則表達式寫出來都比from子句的簡單。
劈分的代碼如下,每個分隔符之間的小部分放在鏈表中:
List<String> ls=new ArrayList<String>();
Pattern p = Pattern.compile(bodySplitPattern,Pattern.CASE_INSENSITIVE);// bodySplitPattern就是劈分的正則表達式
// 先清除掉前後空格
body=body.trim();
Matcher m = p.matcher(body);
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]+");
int arrLength=arr.length;
for(int i=0;i<arrLength;i++){
String temp=FourSpace+arr[i];
if(i!=arrLength-1){
temp=temp+Crlf;
}
ls.add(temp);
}
之所以不直接使用String的split方法是因為分隔符存在多種形式,使用split方法後將無從知曉以前的劈分符是什麼,比如 where c1=1 and c2=2 or c3=3,如果使用(and|or)做劈分符再用split方法,那麼再還原SQL語句是將不可能知道原先的分隔符是and還是or。
5.還原整個SQL語句。
分析完SQL語句後,最終是要以清晰完整的形式將SQL還原出來,這一步的工作主要是將各個子句又重新組合起來,如果分析的語句是
select c1,c2,c3 from t1,t2, t3 where condi1=5 and condi6=6 or condi7=7 order by g1,g2
解析後的的Sql為:
select
c1,
c2,
c3
from
t1,
t2,
t3
where
condi1=5 and
condi6=6 or
condi7=7
order by
g1,
g2
到這裡,我對單句SQL語句進行分析的基本思路都寫完了,下面是完整的代碼示例:
工具類SqlParserUtil,這是進行SQL解析的入口
public class SqlParserUtil{
public static String getParsedSql(String sql){
sql=sql.trim();
sql=sql.toLowerCase();
sql=sql.replaceAll("s+", " ");
sql=""+sql+" ENDOFSQL";
return SingleSqlParserFactory.generateParser(sql).getParsedSql();
}
}
單句Sql解析器基類:
package com.sitinspring.common.sqlparser.single;
import Java.util.ArrayList;
import Java.util.List;
import com.sitinspring.common.sqlparser.SqlSegment;
/** *//**
* 單句Sql解析器,單句即非嵌套的意思
* @author 何楊([email protected])
*
* @since 2009-2-2 下午03:01:06
* @version 1.00
*/
public abstract class BaseSingleSqlParser{
/** *//**
* 原始Sql語句
*/
protected String originalSql;
/** *//**
* Sql語句片段
*/
protected List<SqlSegment> segments;
/** *//**
* 構造函數,傳入原始Sql語句,進行劈分。
* @param originalSql
*/
public BaseSingleSqlParser(String originalSql){
this.originalSql=originalSql;
segments=new ArrayList<SqlSegment>();
initializeSegments();
splitSql2Segment();
}
/** *//**
* 初始化segments,強制子類實現
*
*/
protected abstract void initializeSegments();
/** *//**
* 將originalSql劈分成一個個片段
*
*/
protected void splitSql2Segment() {
for(SqlSegment sqlSegment:segments){
sqlSegment.parse(originalSql);
}
}
/** *//**
* 得到解析完畢的Sql語句
* @return
*/
public String getParsedSql() {
StringBuffer sb=new StringBuffer();
for(SqlSegment sqlSegment:segments){
sb.append(sqlSegment.getParsedSqlSegment()+"n");
}
String retval=sb.toString().replaceAll("n+", "n");
return retval;
}
}
下面是BaseSingleSqlParser的五種子類:
package com.sitinspring.common.sqlparser.single;
import com.sitinspring.common.sqlparser.SqlSegment;
/** *//**
*
* 單句刪除語句解析器
* @author 何楊([email protected])
*
* @since 2009年2月3日8:58:48
* @version 1.00
*/
public class DeleteSqlParser extends BaseSingleSqlParser{
public DeleteSqlParser(String originalSql) {
super(originalSql);
}
@Override
protected void initializeSegments() {
segments.add(new SqlSegment("(delete from)(.+)( where | ENDOFSQL)","[,]"));
segments.add(new SqlSegment("(where)(.+)( ENDOFSQL)","(and|or)"));
}
}
package com.sitinspring.common.sqlparser.single;
import com.sitinspring.common.sqlparser.SqlSegment;
/** *//**
*
* 單句查詢插入語句解析器
* @author 何楊([email protected])
*
* @since 2009年2月3日9:41:23
* @version 1.00
*/
public class InsertSelectSqlParser extends BaseSingleSqlParser{
public InsertSelectSqlParser(String originalSql) {
super(originalSql);
}
@Override
protected void initializeSegments() {
segments.add(new SqlSegment("(insert into)(.+)( select )","[,]"));
segments.add(new SqlSegment("(select)(.+)(from)","[,]"));
segments.add(new SqlSegment("(from)(.+)( where | on | having | groups+by | orders+by | ENDOFSQL)","(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)"));
segments.add(new SqlSegment("(where|on|having)(.+)( groups+by | orders+by | ENDOFSQL)","(and|or)"));
segments.add(new SqlSegment("(groups+by)(.+)( orders+by| ENDOFSQL)","[,]"));
segments.add(new SqlSegment("(orders+by)(.+)( ENDOFSQL)","[,]"));
}
}
package com.sitinspring.common.sqlparser.single;
import com.sitinspring.common.sqlparser.SqlSegment;
/** *//**
*
* 單句插入語句解析器
* @author 何楊([email protected])
*
* @since 2009年2月3日9:16:44
* @version 1.00
*/
public class InsertSqlParser extends BaseSingleSqlParser{
public InsertSqlParser(String originalSql) {
super(originalSql);
}
@Override
protected void initializeSegments() {
segments.add(new SqlSegment("(insert into)(.+)([(])","[,]"));
segments.add(new SqlSegment("([(])(.+)( [)] values )","[,]"));
segments.add(new SqlSegment("([)] values [(])(.+)( [)])","[,]"));
}
@Override
public String getParsedSql() {
String retval=super.getParsedSql();
retval=retval+")";
return retval;
}
}
package com.sitinspring.common.sqlparser.single;
import com.sitinspring.common.sqlparser.SqlSegment;
/** *//**
*
* 單句查詢語句解析器
* @author 何楊([email protected])
*
* @since 2009-2-2 下午03:30:54
* @version 1.00
*/
public class SelectSqlParser extends BaseSingleSqlParser{
public SelectSqlParser(String originalSql) {
super(originalSql);
}
@Override
protected void initializeSegments() {
segments.add(new SqlSegment("(select)(.+)(from)","[,]"));
segments.add(new SqlSegment("(from)(.+)( where | on | having | groups+by | orders+by | ENDOFSQL)","(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)"));
segments.add(new SqlSegment("(where|on|having)(.+)( groups+by | orders+by | ENDOFSQL)","(and|or)"));
segments.add(new SqlSegment("(groups+by)(.+)( orders+by| ENDOFSQL)","[,]"));
segments.add(new SqlSegment("(orders+by)(.+)( ENDOFSQL)","[,]"));
}
}
package com.sitinspring.common.sqlparser.single;
import com.sitinspring.common.sqlparser.SqlSegment;
/** *//**
*
* 單句更新語句解析器
* @author 何楊([email protected])
*
* @since 2009年2月3日9:08:46
* @version 1.00
*/
public class UpdateSqlParser extends BaseSingleSqlParser{
public UpdateSqlParser(String originalSql) {
super(originalSql);
}
@Override
protected void initializeSegments() {
segments.add(new SqlSegment("(update)(.+)(set)","[,]"));
segments.add(new SqlSegment("(set)(.+)( where | ENDOFSQL)","[,]"));
segments.add(new SqlSegment("(where)(.+)( ENDOFSQL)","(and|or)"));
}
}
下面是用於找到具體子類分析器的工廠類:
package com.sitinspring.common.sqlparser.single;
import Java.util.regex.Matcher;
import Java.util.regex.Pattern;
import com.sitinspring.common.sqlparser.exception.NoSqlParserException;
/** *//**
* 單句Sql解析器制造工廠
* @author 何楊([email protected])
*
* @since 2009-2-3 上午09:45:49
* @version 1.00
*/
public class SingleSqlParserFactory{
public static BaseSingleSqlParser generateParser(String sql){
if(contains(sql,"(insert into)(.+)(select)(.+)(from)(.+)")){
return new InsertSelectSqlParser(sql);
}
else if(contains(sql,"(select)(.+)(from)(.+)")){
return new SelectSqlParser(sql);
}
else if(contains(sql,"(delete from)(.+)")){
return new DeleteSqlParser(sql);
}
else if(contains(sql,"(update)(.+)(set)(.+)")){
return new UpdateSqlParser(sql);
}
else if(contains(sql,"(insert into)(.+)(values)(.+)")){
return new InsertSqlParser(sql);
}
//sql=sql.replaceAll("ENDSQL", "");
throw new NoSqlParserException(sql.replaceAll("ENDOFSQL", ""));
}
/** *//**
* 看Word是否在lineText中存在,支持正則表達式
* @param sql:要解析的sql語句
* @param regExp:正則表達式
* @return
*/
private static boolean contains(String sql,String regExp){
Pattern pattern=Pattern.compile(regExp,Pattern.CASE_INSENSITIVE);
Matcher matcher=pattern.matcher(sql);
return matcher.find();
}
}
最後是表示子句的SqlSegment類:
package com.sitinspring.common.sqlparser;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.regex.Matcher;
import Java.util.regex.Pattern;
/** *//**
* Sql語句片段
*
* @author 何楊([email protected])
*
* @since 2009-2-2 下午03:10:29
* @version 1.00
*/
public class SqlSegment{
private static final String Crlf = "n";
private static final String FourSpace = " ";
/** *//**
* Sql語句片段開頭部分
*/
private String start;
/** *//**
* Sql語句片段中間部分
*/
private String body;
/** *//**
* Sql語句片段結束部分
*/
private String end;
/** *//**
* 用於分割中間部分的正則表達式
*/
private String bodySplitPattern;
/** *//**
* 表示片段的正則表達式
*/
private String segmentRegExp;
/** *//**
* 分割後的Body小片段
*/
private List<String> bodyPIEces;
/** *//**
* 構造函數
* @param segmentRegExp 表示這個Sql片段的正則表達式
* @param bodySplitPattern 用於分割body的正則表達式
*/
public SqlSegment(String segmentRegExp,String bodySplitPattern){
start="";
body="";
end="";
this.segmentRegExp=segmentRegExp;
this.bodySplitPattern=bodySplitPattern;
this.bodyPIEces=new ArrayList<String>();
}
/** *//**
* 從sql中查找符合segmentRegExp的部分,並賦值到start,body,end等三個屬性中
* @param sql
*/
public void parse(String sql){
Pattern pattern=Pattern.compile(segmentRegExp,Pattern.CASE_INSENSITIVE);
for(int i=0;i<=sql.length();i++){
String shortSql=sql.substring(0, i);
//System.out.println(shortSql);
Matcher matcher=pattern.matcher(shortSql);
while(matcher.find()){
start=matcher.group(1);
body=matcher.group(2);
end=matcher.group(3);
parseBody();
return;
}
}
}
/** *//**
* 解析body部分
*
*/
private void parseBody(){
List<String> ls=new ArrayList<String>();
Pattern p = Pattern.compile(bodySplitPattern,Pattern.CASE_INSENSITIVE);
// 先清除掉前後空格
body=body.trim();
Matcher m = p.matcher(body);
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]+");
int arrLength=arr.length;
for(int i=0;i<arrLength;i++){
String temp=FourSpace+arr[i];
if(i!=arrLength-1){
temp=temp+Crlf;
}
ls.add(temp);
}
bodyPIEces=ls;
}
/** *//**
* 取得解析好的Sql片段
* @return
*/
public String getParsedSqlSegment(){
StringBuffer sb=new StringBuffer();
sb.append(start+Crlf);
for(String piece:bodyPIEces){
sb.append(pIEce+Crlf);
}
return sb.toString();
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getEnd() {
return end;
}
public void setEnd(String end) {
this.end = end;
}
public String getStart() {
return start;
}
public void setStart(String start) {
this.start = start;
}
}
解析的效果請見:
http://www.blogJava.Net/heyang/archive/2009/02/03/253026.Html