我有一个带有如下查询的DataSet:
select s.name, w.week_ending, w.sales
from store s, weekly_sales_summary w
where s.id=w.store_id and s.id = ?
我想修改查询以允许我指定商店ID列表,例如:
select s.name, w.week_ending, w.sales
from store s, weekly_sales_summary w
where s.id=w.store_id and s.id IN (?)
如何在BIRT中完成此操作?我需要指定什么样的参数?
答案 0 :(得分:9)
简单部分是报告参数:将显示类型设置为列表框,然后选中允许多值选项。
现在困难的部分:遗憾的是,您无法将多值报告参数绑定到数据集参数(至少在版本3.2中没有,这是我正在使用的)。 BIRT World博客上有一篇帖子: http://birtworld.blogspot.com/2009/03/birt-multi-select-statements.html 它描述了如何使用代码插件将多选报表参数绑定到报表数据集。
不幸的是,当我尝试它时,它没有用。如果你可以让它工作,那就是我推荐的方法;如果你不能,那么另一种方法是修改数据集的queryText,在适当的时候将报表参数中的所有值插入到查询中。假设s.id
是数字,这里的函数可以粘贴到数据源的beforeOpen事件脚本中:
function fnMultiValParamSql ( pmParameterName, pmSubstituteString, pmQueryText )
{
strParamValsSelected=reportContext.getParameterValue(pmParameterName);
strSelectedValues="";
for (var varCounter=0;varCounter<strParamValsSelected.length;varCounter++)
{
strSelectedValues += strParamValsSelected[varCounter].toString()+",";
}
strSelectedValues = strSelectedValues.substring(0,strSelectedValues.length-1);
return pmQueryText.replace(pmSubstituteString,strSelectedValues);
}
然后可以从数据集的beforeOpen事件脚本中调用,如下所示:
this.queryText = fnMultiValParamSql ( "rpID", "0 /*rpID*/", this.queryText );
假设您的报告参数名为rpID。您需要将查询修改为如下所示:
select s.name, w.week_ending, w.sales
from store s, weekly_sales_summary w
where s.id=w.store_id and s.id IN (0 /*rpID*/)
脚本中包含0,以便查询脚本在设计时有效,数据集值将正确绑定到报表;在运行时,这个硬编码的0将被删除。
但是,这种方法可能非常危险,因为它可能使您容易受到SQL注入攻击:http://en.wikipedia.org/wiki/SQL_injection,如下所示:http://xkcd.com/327/。
如果从预定义的选项列表中选择纯数值,则不应该进行SQL注入攻击;但是,在允许参数的自由形式输入字符串的情况下,同样的方法很容易受到影响。
答案 1 :(得分:5)
仅供参考:BIRT World文章应该有效(我写过),但这是问题的早期解决方案。
我们已经创建了一个开源插件,您可以将其添加到BIRT,它可以更清晰地解决此问题。 birt-functions-lib中的Bind Parameters函数提供了一种从多值参数中进行多选的简单方法。
如果您仍然感兴趣,请查看Eclipse Labs上的birt-functions-lib project。
答案 2 :(得分:2)
这是另一个。基于我在其他地方找到的一些提示并进行扩展以保留数据集SQL中的参数数量。此解决方案适用于您在数据集的OnBeforeOpen中调用的JavaScript函数:
prepare(this);
function prepare(dataSet) {
while (dataSet.queryText.indexOf("@IN?")>=0) {
dataSet.queryText = dataSet.queryText.replace(
"@XYZ?",
"('"+params["products"].value.join("','")+"') or ?=0"
);
}
}
在您的查询中,将(?)的出现替换为@XYZ?。上面的方法确保了这一点 查询具有实际值并且仍然是参数(因此数据集编辑器和预览不会抱怨)。
注意:注意SQL注入,例如不允许使用字符串值
答案 3 :(得分:2)
我创建了一个更通用的解决方案,它也处理可选/必需参数行为。如果不需要参数且用户未选择任何值,则会禁用IN子句。它还允许用户选择实际值和空值。
在报告initialize
脚本中,我添加了以下代码:
/** Fullfill IN-clause in a data set query,
* using a List box report parameter.
* Placeholder must be the parentheses after IN keyword with wathever you want inside.
* If required is false then the whole IN-clause in the query
* must be surrounded by parentheses.
* dataType and required refers to the parameter, they must be passed,
* but should be better to find a way to retrieve them inside this function
* (given parameter name).
*/
function fulfillInClause(dataSet, placeholder, param, dataType, required) {
if (dataSet.queryText.indexOf(placeholder)>=0) {
var paramValue = params[param].value;
var emptyParam = (paramValue==null || paramValue.length<=0);
//build the list of possible values
// paramValue==null check in ternary operators
// will prevent exceptions when user doesn't select any value
// (it will not affect the query if param is optional,
// while we will never arrive here if it is required)
var replacement = " (";
if (dataType == "string")
replacement += (emptyParam ? "''" : createList(paramValue, ",", "'", "varchar(10)") );
else if (dataType == "integer")
replacement += (emptyParam ? "0" : createList(paramValue, ",", "" , "int" ) );
else
//TODO implement more cases
return;
replacement += ") ";
//if param is not required and user doesn't select any value for it
//then nullify the IN clause with an always-true clause
if (!required && emptyParam)
replacement += " or 0=0 ";
//put replacement in the query
dataSet.queryText = dataSet.queryText.replace( placeholder, replacement );
//DEBUG
params["debug" + dataSet.name + "Query"]=dataSet.queryText;
}
}
/** Create a string list of array values,
* separated by separator and each of them surrounded by a pair surrounders
*/
function createList(array, separator, surrounder, sqlDataType){
var result = "";
for(var i=0; i<array.length; i++) {
if(result.length>0)
result += separator;
if(array[i]!=null)
result += surrounder + array[i] + surrounder;
else
result += "cast(null as " + sqlDataType + ")";
}
return result;
}
在数据集查询中添加特殊的IN子句:
select F1, F2
from T1
where F3='Bubi'
and ( F4 in (''/*?customers*/) )
在带有IN子句的数据集的beforeOpen
脚本中写:
fulfillInClause(this, "(''/*?customers*/)", "customers", "string", false);
请注意,我使用了一个占位符,它允许查询在替换之前运行(例如,它有引号,因为F4是varchar)。 您可以构建适合您案例的占位符。