Как установить параметр для списка значений в отчете BIRT? - PullRequest
8 голосов
/ 04 августа 2010

У меня есть 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 = ?

Я бы хотел изменить запрос, чтобы он позволил мне указать список идентификаторов магазинов, например:

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 ]

9 голосов
/ 04 августа 2010

Простая часть - это параметр отчета: установите тип отображения в поле списка, затем установите флажок «Разрешить множественные значения».

Теперь самая сложная часть: к сожалению, вы не можете связать многозначное значениепараметр отчета для параметра набора данных (по крайней мере, не в версии 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-инъекции не должна быть возможной;тем не менее, тот же подход уязвим, если разрешены строки ввода произвольной формы для параметра.

5 голосов
/ 14 сентября 2010

К вашему сведению: статья BIRT World должна работать (я ее написал), но это было более ранним решением проблемы.

Мы создали плагин с открытым исходным кодом, который вы можете добавить в BIRT, который имеет гораздо более чистое решение этой проблемы. Функция Bind Parameters в birt-functions-lib предоставляет простой способ сделать множественный выбор из многозначных параметров.

Если вам все еще интересно, взгляните на проект birt-functions-lib в Eclipse Labs.

2 голосов
/ 02 августа 2012

Я создал более общее решение, которое обрабатывает необязательные / обязательные параметры поведение тоже. Когда параметр не требуется, и пользователь не выбирает никакого значения, предложение 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*/) )

В beforeOpen сценарии набора данных с предложением IN пишите:

fulfillInClause(this, "(''/*?customers*/)", "customers", "string", false);

Обратите внимание, что я использовал заполнитель, который позволяет выполнить запрос также до замены (например, он имеет кавычки, так как F4 является varchar). Вы можете создать заполнитель, подходящий для вашего случая.

2 голосов
/ 16 апреля 2012

Вот еще один.Основываясь на некоторых подсказках, которые я нашел в других местах и ​​расширил, чтобы сохранить количество параметров в вашем наборе данных SQL.Это решение работает с функцией JavaScript, которую вы вызываете в OnBeforeOpen набора данных:

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, например, не допуская строковые значения

...