优秀的编程知识分享平台

网站首页 > 技术文章 正文

数据库大师成长日记:SQL干货,对字符串另类解析实现复杂功能

nanyue 2024-09-14 06:27:21 技术文章 5 ℃

经常写SQL存储过程的朋友,可能碰碰到这样的场景。比如我们要开发一份报表,利用存储过程返回记录集,但客户端传递过来的过滤条件就很难确定。最常见的过滤条件可能包括日期范围等,但还可能有各种不确定的过滤条件,这对我们写存储过程造成了很大的了压力。

有的朋友可能会把每个条件做为存储过程的一个传入参数,说实话这种方式会要人命的。特别是有的朋友开发的系统中、过滤条件窗口是自动生成的,调整下配置文件,就可以随时调整过滤窗口的过滤项目,过滤项目变了,存储过程的调用接口就变了,那不是每次都要重新编译程序啊?所以这种方式绝对是下下策了。

什么是过滤条件?

有朋友可能会问,什么叫做过滤条件?我本人是开发和实施ERP的,我就从我的系统中抓两张图给大家看一下:

上面两图中,红框框起来的可以称为过滤条件。过滤条件使用的目的,是把过滤的要求交给用户来设定,不同的过滤条件,过滤出不同的数据。

回到正题,如何解决这类问题呢?

笔者常用的方法是:将各种过滤项目和值一起打包成字符串,整体作为存储过程的一个传入参数,在存储过程中通过解析,将过滤项目和值分别再还原回来

问题的核心在于如何解析,我常用的有两种方法,一种方法是采用:

过滤项目1=值1, 过滤项目2=值2 ...

这种方式的特点是将键值用逗号隔开,无论客户端的过滤条件有多复杂,我们只需要修改我们的存储过程就行了,而不需要动不动就要去重新编译客户端程序。

另一种方法是打包成Json传入Json解析方式比较复杂些,但最好用,留下一次再说。这次主要说说第一种方式如何解析。

演示数据准备

为了演示方便,我们先准备一些初始数据,就用我常用的销售表:

declare @sale table(
 FDate smalldatetime,
 FName nvarchar(50),
 FDistrict nvarchar(50),
 FAmount decimal(28,10)
);
insert into @sale
values
('2018-12-27','张三','北京',20000),
('2018-12-27','张三','上海',50000),
('2018-12-27','张三','深圳',40000),
('2018-12-27','张三','广州',30000),
('2018-12-27','李四','北京',30000),
('2018-12-27','李四','上海',50000),
('2018-12-27','李四','深圳',40000),
('2018-12-27','李四','广州',10000);

数据参考下图:

存储过程的传入参数值如下:

FBegDate=2018-12-01,FEndDate=2018-12-31,FName=张三,FDistrict=深圳

对打包过滤条件的解析过程

解析的过程主要用到SQLServer的substring函数,通过while循环进行解析,代码比较长,我把核心的解析过程放进来,如果您刚好对这个有兴趣,拿去稍微改造下就成您的了。先要告诉您的是,变量@memos其实就是上述的存储过程的传入参数,@begdate、@enddate、@name、@district要存放从上述参数中解析出来的开始日期、结束日期、姓名和区域。如果必要参数没有值,为了演示方便我这里直接使用了Print,其实在真正应用时您可以将错误返回。这个脚本稍微改造下,对MySQL、PostgreSQL、FirebirdSQL、Oracle都是通用的。

--解析使用的变量
declare 
 @pos bigint,
 @beg bigint,
 @end bigint,
 @str nvarchar(1000),
 @char nvarchar(1),
 @memos nvarchar(2000),
 @key nvarchar(30),
 @field nvarchar(50),
 @val nvarchar(50);
--获取的数据
declare 
 @begdate smalldatetime,
 @enddate smalldatetime,
 @name nvarchar(100)=space(0),
 @district nvarchar(100)=space(0);
--初始化传入变量
set @memos='FBegDate=2018-12-01,FEndDate=2018-12-31,FName=张三,FDistrict=深圳';
--解析过程
set @pos=1;
set @beg=1;
set @key=space(0);
set @memos=@memos+',';
set @end=len(@memos);
set @field=space(0);
set @val=space(0);
set @str=space(0);
while @pos<=@end begin --将所有的部分循环完全
 set @char=substring(@memos,@pos,1);
 if @char=',' begin
 set @val=@str;
 set @str=space(0);
 if @field='FBegDate' begin
 if ISDATE(@val)=1 begin
 set @begdate=convert(datetime,@val);
 end else begin
 print '需要设置开始日期!';
 end
 end else
 if @field='FEndDate' begin
 if ISDATE(@val)=1 begin
 set @enddate=convert(datetime,@val);
 end else begin
 print '需要设置结束日期!';
 end
 end else
 if @field='FName' begin
 set @name=@val;
 end else 
 if @field='FDistrict' begin
 set @district=@val;
 end
 end else
 if @char='=' begin
 set @field=@str;
 set @val=space(0);
 set @str=space(0);
 end else begin
 set @str=@str+@char;
 end
 set @pos=@pos+1;
end

解析之后的过滤项目值的使用

等到您需要的过滤项目值解析出来了,您就可以在SQL查询中使用了:

select 
 FDate as F日期,
 FName as F姓名,
 FDistrict as F区域,
 FAmount as F销售额
from @sale
where FDate>=@begdate
 and FDate<=@enddate
 and FName=(case when len(@name)>0 then @name else FName end)
 and FDistrict=(case when len(@district)>0 then @district else FDistrict end);

上述的脚本里面,大家可能会对case语句有些不解,其目的是判断是否传来了可用的姓名或区域,如果没有就不参与过滤的意思。

在头条中放太多代码看起来太费劲,我把完整的代码截图下来,供有兴趣的朋友参考:

需要完整脚本的朋友,可以在下面留下邮箱,我会发给您。

希望对您有所帮助!

最近发表
标签列表