如何将参数设置为BIRT报告中的值列表?

时间:2010-08-04 02:22:20

标签: birt

我有一个带有如下查询的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中完成此操作?我需要指定什么样的参数?

4 个答案:

答案 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)。 您可以构建适合您案例的占位符。