程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> SqlServer數據庫 >> 關於SqlServer >> 單句SQL語句的解析方案

單句SQL語句的解析方案

編輯:關於SqlServer


  數據是程序處理的主要內容,它一般存儲在關系型數據庫中,要操作它們最終必須要通過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

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved