众所周知,大拿们在设计报表的时候,需要展现的数据很少是固定不变地,经常需要使用查询条件过滤出我们想要的数据。简而言之就是,同一张报表可以在不同的参数控制下呈现出不同的数据。
那么问题来了,报表工具一般都用哪些方法来实现这一需求呢?
使用SQL参数
首先当然必须是说 SQL, 正常情况下不同的过滤条件会对应着取数 SQL 中不同的 WHERE 条件,而报表工具能将报表参数对应成 SQL 的参数,这样一来,输入不同报表参数时就会产生不同的 SQL。
假如我们要做一个有查询条件的报表,拿订购日期作为检索条件,查询不同日期区间下订单信息表中的数据。其中的数据列包括:订单 ID, 客户 ID, 订购日期, 发货日期, 运货商, 货主名称, 货主地区, 订单金额。
Follow me,我们拿润乾报表来做个例子,首先设置两个参数,取名为 startdate 与 enddate:
然后这个报表的取数 SQL 要写成含有参数的形式,SQL 语句中使用“?”(英文问号)表示参数:
select 订单 ID, 客户 ID, 订购日期, 发货日期, 运货商, 货主名称, 货主地区, 订单金额 from 订单信息表 where 订购日期 >? and 订购日期 <?
然后要配置 SQL 参数与报表参数的对应,即将 SQL 语句中每个“?”翻译成对应的报表参数。
配置完成后用户只需要通过设置不同的报表参数,就可以获取不同的数据了。例如查询 2014 年 1 月 1 号到 2014 年 1 月 8 号之间的订单信息,那么设置参数 startdate 值为 2014-01-01,参数 enddate 值为 2014-01-08,查询结果如下:
空值参数
有时候我们希望某个参数不输入时报表可以聪明地理解为忽略该参数,比如上例中没有输入 enddate 时报表可以默默地查出从某个起始日期之后的所有订单。那需要怎么做呢?
这种情况下可以把没有输入的参数认为是取值为空的参数,我们只需要修改一下上面的 SQL 语句,增加对空值的判断即可:
select 订单 ID, 客户 ID, 订购日期, 发货日期, 运货商, 货主名称, 货主地区, 订单金额 from 订单信息表 where (? is null or 订购日期 >?) and (? is null or 订购日期 <?)
这样,当参数值为空时就相当于被忽略了。
相应地,SQL 参数与报表参数的对应也要修改,这两个参数在 SQL 中分别使用了两次,各自对应了两个问号:
这时候要查询 2015 年 1 月 1 号之后、结束日期不限制的数据,将参数 startdate 设置为 2015-01-01,参数 enddate 为 null 就可以了,
查询结果如下:
使用宏表达式
从上例可以看到,使用参数时要事先知道取数 SQL 中哪些字段可作为查询条件。但有时候可用于做查询条件的字段非常多,比如针对一个订货信息表来说,还可能按客户 ID、按地区、按运货商等各种条件查询。如果使用上述参数的方式,就需要设置很多的参数,取数 SQL 语句也很长,使用和维护都比较麻烦。如果实现按任意字段查询的话,无疑会方便许多。
有些报表工具提供了宏表达式,可以更灵活地解决这个问题。仍然以润乾报表为例说明:
首先在报表中定义一个字符串参数,比如名称为 search。然后在取数 SQL 中使用宏表达式引用这个参数:
select 订单 ID, 客户 ID, 订购日期, 发货日期, 运货商, 货主名称, 货主地区, 订单金额 from 订单信息表 where ${search}
其中 ${search} 的意思就是会将 search 的内容拼接到 SQL 的相应位置得到一句可执行的 SQL 语句。这种写法即称为宏表达式(下面讨论时就简称宏)。
现在给报表传递不同的参数值就可以得到不同的数据了。例如要查询订单日期为 2014 年 1 月 1 号到 2014 年 1 月 8 号且货主地区为华北的订单信息,那么设置 search 的值为:
订购日期 >’2014-01-01′ and 订购日期 <‘2014-01-08′ and 货主地区 =’华北’
查询结果如下:
从上可以看出利用宏可以在报表中轻易替换 SQL 语句的部分甚至全部,不同参数值,在报表中可以计算出不同的表达式结果,从而得到不同的报表信息,这样面对大量不同类型的报表,用户再也不用每次都做单独的设计和维护了,大大减轻了工作量,释放了劳动力。
参数与宏的对比
既然利用宏可以在报表中动态替换 SQL 语句的部分甚至全部,而且显然使用宏比 SQL 参数更灵活,另外参数能做到的事情用宏都能做到,那么报表工具还有必要提供 SQL 参数机制吗?是不是只要有宏就够了?
事实上并没有这么简单。宏只是简单地替换 SQL 语句的部分,要保证最后的语句合法,有时在生成参数字符串会有麻烦。数值等简单数据类型可以直接拼进 SQL,而字符串常数就需要加上引号才可以,万一这个常数中本身已经有引号,则还需要先进行转义动作,而不同数据库的规则又并不完全一样,这就会影响报表的兼容性。
日期类型参数是最常出现这种麻烦的情况,例如查询订购日期为 2014-12-01 号的订单信息,如果使用 Oracle 数据库,那么宏值写法是:
订购日期 =TO_DATE(‘2014-12-01′,’yyyy-mm-dd’)
如果使用 Mysql 数据库,那么宏值写法为:
订购日期 = DATE_FORMAT(‘2014-12-01′,’yyyy-mm-dd’)
使用 SQL 参数就没有这个问题,数据库接口能直接识别各种数据类型的参数, 直接传入参数值即可,没必要再做这种拼串动作,也就不存在兼容性的问题了。
宏的SQL植入风险
使用宏还存在安全风险,WEB 中使用报表时,参数可能是网页生成并传到后台的,而网页有可能是被黑客攻击改写的,那么就有可能将恶意的参数值传到后台拼入 SQL 语句,欺骗服务器执行恶意 SQL 命令。这就是我们常说的 SQL 植入风险。
比如,在前面的宏表达式示例中,如果有人将 search 值改写成:
1=1 UNION SELECT userid,password,null,… FROM user
那么就可能造成关键信息泄露。解决这个问题并不很简单,把原始 SQL 改写成这样
select … where (${search})
看起来是可以挡住上面那种攻击了,但仍然可以设计出新的植入串攻击(读者有兴趣可以作为一个练习题来试试),要改成为这种很麻烦的样子才行:
select…from…where(${search}) and ${search}
显然,这种语句需要有强烈安全意识的老司机才能做到,而一般报表开发人员这方面相对会弱一些。所以宏不能乱用,能不用则不用。而使用 SQL 参数就没有这个风险,虽然参数没有宏灵活,但显然安全性对报表工具是必不可少的。个别报表工具为了省事只提供宏实现查询条件,看起来是灵活了,但其实是相当不负责任的做法,在选择报表工具时一定要注意。
润乾报表解决方案
如果使用润乾报表,上面的问题就简单多了,润乾报表 5 中新增了对参数检验的功能,这就即可享受宏的灵活性,又能很轻松地挡住这种攻击。仍以上面的业务场景为例,实现步骤如下:
1、制作有宏表达式的报表
在报表中定义一个宏命名为:search,数据集 SQL 为:
select 订单 ID, 客户 ID, 订购日期, 发货日期, 运货商, 货主名称, 货主地区, 订单金额 from 订单信息表 where ${search}
2、配置参数中的禁用单词
在【安装目录】\esProc\config\raqsoftConfig.xml 文件中的 disallowedParamWordList 属性里配置禁用字符串,配置多个字符串时使用逗号分隔,例如
<property name=”disallowedParamWordList” value=”union, user”/>
将会在参数中禁用“union”与“user”,即含有这些串的参数将被拒绝。
3、在 tag 标签上设置检查
用 tag 标签 paramCheck 来控制报表是否要做此校验,paramCheck=”yes”表示接受校验,paramCheck=”no”表示不作校验,不配置该标签时默认为接受校验。这样,不是所有报表都一定会被检查,有些可以确认安全的报表就不必再检查以获得灵活性。
这些都完成后,在 web 端浏览报表时,用户输入的检索条件会在报表参数校验通过后才传递给报表,最终拼接到数据集 SQL 中,不合法的参数值将被拒绝,从而避免了上例中 SQL 植入的风险。