IF-ELSE Alternative for Multiple SQL criteria for use in BIRT - sql

I want to create a report by using BIRT. I have 5 SQL criterias as the parameter for the report. Usually when I have 3 criterias, I am using nested if-else for the WHERE statement with javascript.
Since right now I have more criteria it becomes more difficult to write the code and also check the possibilities, especially for debug purposes.
For example the criteria for table employee, having these 5 criterias : age, city, department, title and education. All criteria will be dynamic, you can leave it blank to show all contents.
Do anyone know the alternative of this method?

There is a magical way to handle this without any script, which makes reports much easier to maintain! We can use this kind of SQL query:
SELECT *
FROM mytable
WHERE (?='' OR city=? )
AND (?=-1 OR age>? )
AND (?='' OR department=? )
AND (?='' OR title=? )
So each criteria has two dataset parameters, with a "OR" clause allowing to ignore a criteria when the parameter gets a specific value, an empty value or a null value as you like. All those "OR" clauses are evaluated with a constant value, therefore performances of queries can't be affected.
In this example we should have 4 report parameters, 8 dataset parameters (each report parameter is bound to 2 dataset parameters) and 0 script. See a live example of a report using this approach here.
If there are many more criteria i would recommend to use a stored procedure, hence we can do the same with just one dataset parameter per criteria.
Integer parameter handling
If we need to handle a "all" value for an integer column such age: we can declare report parameter "age" as a String type and dataset parameters "age" as an integer. Then, in parameters tab of the dataset use a value expression instead of a "linked to report parameters". For example if we like a robust input which handles both "all" "null" and empty values here is the expression to enter:
(params["age"].value=="all" || params["age"].value=="" || params["age"].value==null)?-1:params["age"].value
The sample report can be downloaded here (v 4.3.1)

Depending on the report requirements and audiance you may find this helpful.
Use text box paramaters and make the defualt value % (which is a wild card)
SELECT *
FROM mytable
WHERE city like ?
AND age like ?
AND department like ?
AND title like ?
This also allows your users to search for partial names. if the value in the city text box is %ville% it would return all the cities with "ville" anyplace in the city name.

If report parameters to be included in SQL-WHERE clause would be named according to some naming convention, for instance query_employee_[table column name], you could write Java-Script code in a generic way, so that you will not have to change it when new reporters being added.
for each param in params {
if param.name starts with query_employee_ {
where_clause += " and " + param.name.substring(after query_employee) + " == '" + param.value + "'";
}
}
You will have to check type of a parameter to make a decision whether you have to quote the parameter value.
The event handler could look as follows (implemented in Java, but it should be possible to port it to JavaScript, if you really need it to be in JavaScript):
public class WhereConditionEventHandler extends DataSetEventAdapter {
#Override
public void beforeOpen(IDataSetInstance dataSet,
IReportContext reportContext) throws ScriptException {
super.beforeOpen(dataSet, reportContext);
String whereClause = " where 1 = 1 ";
SlotHandle prms = reportContext.getDesignHandle().getParameters();
for (int i = 0; i < prms.getCount(); i++) {
if (prms.get(i) instanceof ScalarParameterHandle) {
ScalarParameterHandle prm = (ScalarParameterHandle) prms.get(i);
int n = prm.getName().indexOf("sql_customer_");
if (n > -1) {
String prmValue = "" + reportContext.getParameterValue(prm.getName());
if (DesignChoiceConstants.PARAM_TYPE_STRING.equals(prm.getDataType())) {
prmValue = "'" + prmValue + "'";
}
whereClause += " and " + prm.getName().substring("sql_customer_".length()) + " = " + prmValue;
}
}
}
System.out.println("sql: " + whereClause);
dataSet.setQueryText(dataSet.getQueryText() + whereClause);
}
}
By the way, you can pass in parameters that are not registered as report parameters in the BIRT report design. BIRT will nevertheless put them into "params" array.

Related

SqlCommmand parameters not adding to UPDATE statement (C#, MVC)

If you look at the stuff commented out, I can easily get this to work by adding user input directly in to the query, but when I try to parameterize it, none of the values are being added to the parameters...
This code is throwing an error
Must define table variable #formTable
but the issue is none of the values are adding, not just the table variable (verified by replacing table name variable with static text).
I have many insert statements in this project structured exactly like this one which work perfectly. What am I doing wrong here?
string constr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
using (SqlConnection con = new SqlConnection(constr))
{
//string query = "UPDATE " + s.formTable + " SET " + s.column + " = '" + s.cellValue + "' WHERE MasterID = '" + s.id + "'";
string query = "UPDATE #formTable SET #column = #cellValue WHERE MasterID = #id;";
using (SqlCommand cmd = new SqlCommand(query))
{
//SqlParameter param = new SqlParameter("#formTable", s.formTable);
//cmd.Parameters.Add(param);
cmd.Parameters.AddWithValue("#formTable", s.formTable);
cmd.Parameters.AddWithValue("#column", s.column);
cmd.Parameters.AddWithValue("#cellValue", s.cellValue.ToString());
cmd.Parameters.AddWithValue("#id", s.id.ToString());
cmd.Connection = con;
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
Parameters are for values, not object identifiers (tables, columns, etc.), so the only valid parameters you have are #cellValue and #id.
If you want to dynamically set table/column names based on user input, you're likely looking at string concatenation. However, that doesn't necessarily mean SQL injection. All you need to do is validate the user input against a set of known values and use the known value in the concatenation.
For example, suppose you have a List<string> with all of your table names. It can be hard-coded if your tables are never going to change, or you can make it more dynamic by querying some system/schema tables in the database to populate it.
When a user inputs a value for a table name, check if it's in the list. If it is, use that matching value from the list. If it isn't, handle the error condition (such as showing a message to the user). So, even though you're using string concatenation, no actual user input is ever entered into the string. You're just concatenating known good values which is no different than the string literals you have now.

How to compare with the string when the same string received in different order in SQL

How to compare with the string when the same string received in different order?
Eg: in my table there is two columns named as "meaning","Relevant name"
column data type is in varchar
meaning - Relevant name
food - snacks;choco;chips
input - " choco;chips;snacks "
output - "food"
how this type string will be compared?Could anyone suggest any idea
Putting CSV data in a column is bad; relational databases aren't designed to be treated that way. Have multiple rows for your values:
Meaning|RelName
food |snacks
food |choco
food |chips
And split your input when querying:
SELECT DISTINCT Meaning FROM t WHERE RelName IN ('choco','chips','snacks')
The split and query formation should be done in your frontend language
Do not use string concatenation to build your SQL with values, i.e. this naive way (c# syntax):
//this is bad - forming a list of values by replacing ; with',' and concatting
//into anotehr sql string
strSQL = "SELECT DISTINCT Meaning FROM t WHERE RelName IN ('" + input.Replace(";", "','") + "')";
Do use parameters together with string concatenation:
//this is good
//declare the base sql stub
sqlCommand.CommandText = "SELECT DISTINCT Meaning FROM t WHERE RelName IN (";
//split the input into values
var foods = input.Split(';');
//for each value
for(int i = 0; i < foods.Length; i++){
//add a bit to the sql with a new parameter named
sqlCommand.CommandText += "#param" + i + ",";
//add a parameter name and value to match the just-added parameter
sqlCommand.Parameters.AddWithValue("param" + i, foods[i]);
}
//the sql ends with a comma; a syntax error. trim it and put a ) to close the IN
sqlCommand.CommandText = sqlCommand.CommandText.TrimEnd(',') + ")";

Dynamically create sql where condition on textbox entry

I am working on a C# desktop application. I want to create a search functionality. Now the problem is that i am using around 8 textboxes. Different permutations of textboxes could be populated and the resulting 'sql where' condition should only include those textboxes values which are not null. Now one pathetic way is to use a zillion 'if and else' which obviously is laborious. Any other way to do this?
You need just one query with filled WHERE to use all parameters like this
select ...
from ...
WHERE
(firstNameColumn=:firstNameParam or :firstNameParam is null)
AND (lastNameColumn=:lastNameParam or :lastNameParam is null)
AND (...)
I would like to make a point of first checking is the paramtere null, then use it to compare with column values.
Since you are generating query in C#, try old-Chinese approach from Ming period of using default condition where 1=1 just to avoid checking did you already had first condition :)
string query = "select ... from ... join ... on ... where 1=1";
//suposedly you have value of one search box in variable called "item_name"
if(string.IsNullOrWhiteSpace(item_name) == false)
{
query += " and Order_Line.Name ='" + item_name + "'";
}
and so on for other fields.
What you are trying to do in order to avoid ifs is not really a good approach. Look at this:
string query = " select ... where Order_Line.Name = '" + item_name + "'";
What will be the resulting string if item_name is actually null?
EDIT: the resulting query would be
where Order_Line.Name = '' or Order_Line.Name is null
which is not what you want. You want every row if that search field is empty, menaing it shouldn't have anu effect on search. That's why you need condition to see will you include this column in where clause in the first place.

Defining an expression or code with lookup RS

I need to migrate a CR report to RS.
I have two Methods, that are basically two queries, one is called "Products" the other one is called "Volume".
Both these datasets have a field called ID.
The idea is to count ProductsID = VolumeID. Can I use a lookup as an expression inside RS that counts when lookup is not equal to Nothing? Or I have to do a code to do that?
EDIT:
Let me add more information.
Crystal Report is managing these in the Data Layer of the web app like this:
DataRow drVol = vols.Rows.Find(new Object[] { id, idEzd });
if (drVol != null)
{
if (drVol["Volume"] != System.DBNull.Value)
cRow.Volume = (isNormalReport ? Convert.ToDecimal(drVol["Volume"]) : Convert.ToDecimal(drVol["Volume"]));
else
cRow.Volume = 0;
dset.Compradores.Rows.Add(cRow);
}
So in this case I verified that RS is giving me an X amount of rows while CR is giving me another amount of rows.
The Idea is to duplicate this on an expression or VB code inside RS.
As you can see, we have to filter the drVol != null, RS is bringing all the rows.
I know a Lookup function can be used in this case as:
Lookup(Fields!id.Value & Fields!idEzd.Value, Fields!id.Value & Fields!idEzd.Value,Fields!Givemethisfield.Value,"Dataset")
But can I use this Lookup expression inside a code to count?
The counter should be something like this (Then I should add the lookup or an if that equals that vlookup, can the code see the two datasets?):
Dim count=0 as integer
Public Function Counter() as Integer
count = count + 1
return count
End Function
Public Function GetCounter () as integer
return count
End Function
Then create a Calculated Field for example "Counter" with the expression
=Code.Counter()
And then make the sum of that calculated field:
=Sum(Fields!Counter.Value)
But is not working, not even the counter, is giving me 0.
Sorry if I made a mistake in the Visual Basic code, I don't program in Visual.
EDIT2:
I saw some workaround on MSDN, something like:
Lookup(Fields!id.Value & Fields!idEzd.Value, Fields!id.Value & Fields!idEzd.Value,Fields!Givemethisfield.Value,"Dataset").Lenght
It makes sense as a lookup is an Array.
The problem is, either if I choose one scope or the other, I still have the problem that some fields can't be seen for example if the fields id and idezd are on Dataset1 and Givemethisfield is on Dataset2, it will either not see the id,idEzd or Givemethisfield.

Cayenne - Search for combination of dependencies

I'm creating an application that allows users to create a form, which can then be loaded and filled out by another user, and that user's submission can then be viewed.
A Form is filled with Fields. When a user fills the form, a Submission database object is created, and this submission has a 1-M relationship with FieldValue objects. A FieldValue object has a FK to a Field, and stores a String of the user's input. Using this design, to view a submission, I read through the FieldValues associated with the Submission, and load the associated Field object, and fill it with the user's input. Everything works well in this sense, but my problem is in searching for these submissions.
I'm working on a search page, where I dynamically creat search fields based on the Fields of the Form that's being searched on. For example firstName and lastName. Let's say that the user searches with firstName = j lastName = smith. Using these search fields, I want to search for all submissions that have a FieldValue where the FK matches to firstName and the text contains "j" AND has A DIFFERENT FieldValue where the FK matches to lastName and the text contains "smith"
I have been trying variations of the following code:
Expression exp = ExpressionFactory.matchExp(Submission.FORM_PROPERTY, _formId);
for (SearchField searchField : searchFields)
{
Expression fieldExp = ExpressionFactory.matchExp(Submission.FIELD_VALUE_PROPERTY +"." + FieldValue.FIELD_PROPERTY, searchField.getFieldId());
fieldExp = fieldExp.andExp(ExpressionFactory.likeIgnoreCaseExp(Submission.FIELD_VALUE_PROPERTY +"." + FieldValue.TEXT_PROPERTY, "%" + searchField.getText() + "%" ));
exp = exp.joinExp(Expression.AND, fieldExp);
}
SelectQuery query = new SelectQuery(Submission.class, exp);
What I'm trying to do is loop through each of the search fields, and add it to the list of FieldValues that must be in the Submission. The problem with this is that it keeps searching for ONE FieldValue that has all of those values, and so, obviously fails. I have never done a search that could be a 1-M within another class, so I assume that I'm missing something here. Any help would be greatly appreciated. I apologize for the small novel in trying to describe what's going on, but it's a bit out of the ordinary for me.
You will need to build an Expression that creates M joins. "Splits" and "aliases" control how joins are generated. Since you have more than one criteria for each join, splits won't work, so using explicit aliases is more appropriate. Just let SelectQuery know what each alias means.
import static org.apache.cayenne.exp.ExpressionFactory;
int len = searchFields.size();
String[] aliases = new String[len];
for (int i = 0; i < len; i++) {
SearchField f = searchFields[i];
aliases[i] = f.getFieldId();
Expression e = matchAllExp(alias +"." + FieldValue.FIELD_PROPERTY, f.getFieldId());
e = e.andExp(likeIgnoreCaseExp(alias +"." + FieldValue.TEXT_PROPERTY, "%" + f.getText() + "%" ));
exp = exp.joinExp(Expression.AND, e);
}
SelectQuery query = new SelectQuery(Submission.class, exp);
query.aliasPathSplits(Submission.FIELD_VALUE_PROPERTY, aliases);