一、开发初衷

相信大家在平时写SQL语句的时候,为了节省时间,都不太喜欢把小指放到Shift键上打出关键字的大写形式,比如建数据库,直接就create database…了,而不是CREATE DATABASE,反正SQL SERVER都可以执行语句,所以也就无所谓代码的效果了。

的确如此,但是编程都有规范一说,在SQL的编写中,将关键字大写就是一个规范,所以如果有这样的工具能够方便象我也是这样的懒人,批量将关键字替换成大写那就好了,不过饭来张口、衣来伸手可不是什么好习惯,所以还是DIY吧。。。

二、开发思路

建立Winform应用程序,依据需求此工具需要实现三个方面:

1、打开对话框——选择要处理的SQL文件路径;

2、保存对话框——选择处理完成后输出文件的路径;

3、核心功能——用正则表达式查找SQL文件中的关键字,如有匹配则将其转换为大写。

三、开发过程

俗话说:计划赶不上变化。本来计划一两个小时就能够搞定,但是还是足足折腾了一个下午,实在有些汗颜,下面就详细叙述一下过程吧:

1、搜集SQL关键字

要说SQL关键字,除了大家常见的CRUD相关的语句,还有FUNCTION、CURSOR、PROCEDURE、系统内置VARIABLE、VIEW、SCHEMA等等等等,总之一大堆,而且貌似也超出关键字的范畴了,要想完全搞定这么多东西,俺实在只能说无能为力了,所以我只搜集了大家平时经常用到的一些关键字,在下面的代码中会看到。

PS:当然如果园子里哪位能够做出一个覆盖所有SQL那样强大的来,小弟一定前往拜读。

2、SQL文件语句的遍历

不像ORACLE里的SQL PLUS那样难用的东西(主要是俺太菜了,经常出纰漏,很难一下就写对SQL),在SQL SERVER中SQL语句可以上下键随意更改内容或格式,而且语句也不用强制分号作为结尾,所以复杂的时候几行甚至十几行才是一个完整的SQL语句,所以一开始我就尝试从头到尾遍历整个文件貌似有些不太现实,最后的方案是采用了按行划分,以一行为一个数组进行处理,代码如下:

    //先以行作为划分,得到一个数组 RegexrowReg=newRegex("\r\n"); string[]strRow=rowReg.Split("转换的SQL源文件内容");

3、现在依然不能急着处理strRow数组中的每一个元素,因为元素中有可能会出现” create database “这样的情况,即关键字两侧有一个或多个空格字符,如果不进行统一处理,会给后面的替换带来很大的麻烦,所以这里再用一个正则表达式,使空格通通变成一个,随后再以空格作为分割符,这样才算得到了我们真正需要进行匹配替换操作的数组:

    for(inti=0;i<strRow.Length;i++) { strRow[i]=Regex.Replace(strRow[i],@"\s+",""); string[]strRowDetail=strRow[i].Split(newchar['\0']); }

4、下面循环遍历strRowDetail数组中的每一项:

    for(intj=0;j<strRowDetail.Length;j++) { if(regex.regRow.IsMatch(strRowDetail[j])) { Matchmatch=regex.Match(strRowDetail[j]); ... } }

为什么暂时省略了后面的代码呢,因为这样写考虑是不周全的,试想如果一个SQL语句如:

    createtablestudent ( cnovarchar(50), ... )

这样关键字一个是一个的话,那匹配起来自然没有问题,但很多时候事情并没有那么简单,有些SQL语句包含了很多关键字。

最典型的就是日期类型的操作,比如:

    --求本月天数:selectday(dateadd(mm,1,getdate())-day(getdate()));

这样的一个SQL语句我们以空格划分后,那么数组的第二个元素会是day(dateadd(mm,1,getdate())-day(getdate())),如果进行上述的Match匹配就只会将第一个day变为大写,而其他则没有得到处理。

进行了一定的测试后,我转用了MatchCollection类,代码如下(注:tbSource指的是显示SQL代码的文本框):

    for(intj=0;j<strRowDetail.Length;j++) { //regex.regRow指的是寻找匹配关键字的正则表达式,在代码下载中会看到 //首先查看strRowDetail[j]是否能够匹配 if(regex.regRow.IsMatch(strRowDetail[j])) { //若能匹配则记录其匹配的内容 MatchCollectionmc=regex.regRow.Matches(strRowDetail[j]); //遍历匹配的集合 for(intk=0;k<mc.Count;k++) { //循环替换匹配的项 strRowDetail[j]=strRowDetail[j].Replace(mc[k].Value,mc[k].Value.ToUpper()); } //附加替换完成后的内容 this.tbSource.Text+=strRowDetail[j]+""; } //不匹配则直接附加原来的内容 else{ this.tbSource.Text+=strRowDetail[j]+""; } } //因为最初是以行分割的,所以这里要附加\r\n,从而保持其原来的格式 this.tbSource.Text+="\r\n";

5、最后用StreamWriter将得到的内容写入文件,这里除了编码问题貌似也没啥好说的了:

    StreamWriterwriter=newStreamWriter(this.tbExportPath.Text,false,Encoding.Default); writer.WriteLine(this.tbSource.Text); writer.Flush();

四、效率测试

细心的你在阅读代码的过程中可能已经发现我在大量的字符串处理操作时一直在使用普通的字符串”+=”这样的方法,其实这也是我为我的效率小测验准备的,这下可以再切实的体会一下StringBuilder对效率的提升。

要测试那还是要请出我们的Stopwatch了,相信大家都会的了,下面的代码直接无视:

    StreamWriterwriter=newStreamWriter(this.tbExportPath.Text,false,Encoding.Default); writer.WriteLine(this.tbSource.Text); writer.Flush();

如果使用原来普通字符串连接的方法,足足等了我5347毫秒!

而如果使用StringBuilder对象作字符串操作,并且为提高for循环效率,避免每次重复计算数组长度,先将数组的长度存储在变量中,优化代码如下:

    intstrRowDetailLength=strRowDetail.Length; for(intj=0;j<strRowDetailLength;j++) { if(regex.regRow.IsMatch(strRowDetail[j])) { MatchCollectionmc=regex.regRow.Matches(strRowDetail[j]); intmcCount=mc.Count; for(intk=0;k<mcCount;k++) { strRowDetail[j]=strRowDetail[j] .Replace(mc[k].Value,mc[k].Value.ToUpper()); } strBuilder.Append(strRowDetail[j]+""); } else15{ strBuilder.Append(strRowDetail[j]+""); } } strBuilder.Append("\r\n");

测试的结果是34毫秒,hoho,StringBuilder的威力果然很强大。。。

五、项目说明

1、介于正则表达式实在是碍眼,所以就没有贴出来,说实话我自己都不忍心看了。。。

2、麻雀虽小也要五脏俱全嘛,这个小工具在界面上使用了钱李峰同学的 美化版Winform,没经作者同意就乱打广告,忘作者不要介意啊 ^_^

六、后续问题

1、希望大家在看完后能够指出我在解决的思路上有没有什么问题,有没有什么简单的方法,因为虽然实现了,但个人感觉方法上有点龊。。。

2、期待哪位能够开发出更完善更优雅的SQL关键字替换工具。。。

3、如有使用问题请及时留言。。。

七、项目下载

http://files.cnblogs.com/RockyMyx/Solution.rar

原文标题:DIY小工具开发—SQL关键字批量转换

链接:http://www.cnblogs.com/RockyMyx/archive/2010/04/21/Convert-Sql-Keyword.html

【编辑推荐】

    SQL Server使用索引实现数据访问优化SQL Server数据库优化经验总结如何使用SQLServer数据库查询累计值浅析Oracle和SqlServer存储过程的调试、出错处理几段SQLServer语句和存储过程50种方法优化SQL Server数据库查询