JbdcTemplate - PreparedStatements with Dynamic SQL Query - sql

I know jdbcTemplate can be used to create PreparedStatements if you set it up to do so:
i.e.
private JdbcTemplate jdbcTemplate;
String sqlQuery = "Select * from table where column = ?";
String value = "value";
this.jbdcTemplate.query(sqlQuery, new Object[] { value }, rs, rowNum -> {
System.out.println(rs.getString("column"));
});
However, I have a query with many AND operators in the Where clause, and depending on some condition, a specific AND statement may or may not be added to the query string.
i.e.
private JdbcTemplate jdbcTemplate;
StringBuilder sqlQuery = new StringBuilder("Select * from table where 1=1 "); //base query
if(someCondition)
sqlQuery.append("And column1 = '" + value1 + "'");
if(someCondition2)
sqlQuery.append("And column2 = '" + value2 + "'");
if(someCondition3)
sqlQuery.append("And column3 = '" + value3 + "'");
//etc...
With this type of dynamic query, I am unable to know exactly how many values to place in the new Object[] {} field at compile time, because the someCondition fields are always changing at runtime.
Is there a way to write the this.jdbcTemplate.query(sqlQuery.toString(), new Object[] {}... to accommodate these dynamic AND statements?

I found a solution after taking into consideration what #mustaccio said in his comment to my original question. I also took part of the solution from this stackoverflow question and used it in my own solution.
The main issue I was having was dynamically creating an Object[] array at runtime, since you can't dynamically add elements to an Object[] array. They must have a defined size when initialized.
First, I create an arraylist of strings called queryArgs. Every time one of the if conditions proves true and we add an AND statement to the query, I also add another line of code that adds the value to be plugged in the preparedStatement to the queryArgs arraylist. Once that's done, I create a new Object[] array whose size is initialized to the size of the queryArgs arraylist. Lastly, I loop through each element in the Object[] array, setting them equal to the values in queryArgs.
private JdbcTemplate jdbcTemplate;
List<QueryResults> jdbcQuery(QueryParams queryParams) {
/* base query */
StringBuilder sqlQuery = new StringBuilder("Select * from table where 1=1 ");
/* stores the dynamic preparedStatement arguments */
List<String> queryArgs = new ArrayList<>();
if(someCondition){
sqlQuery.append("And column1 = ? ");
queryArgs.add(queryParams.value1);
}
if(someCondition2){
sqlQuery.append("And column2 = ? ");
queryArgs.add(queryParams.value2);
}
if(someCondition3){
sqlQuery.append("And column3 = ? ");
queryArgs.add(queryParams.value3);
}
//etc...
/* this is the part I used from the above stackoverflow question */
Object[] preparedStatementArgs = new Object[queryArgs.size()];
for(int i = 0; i < preparedStatementArgs.length; i++){
preparedStatementArgs[i] = queryArgs.get(i);
}
/* Lastly, execute the query */
return this.jdbcTemplate.query(sqlQuery.toString(),
preparedStatementArgs, (rs, rowNum) -> {
QueryResults result = new QueryResults();
/* store the results of the query... */
});
}
The outlier is that one of the dynamic AND statements above is written like this:
AND column4 IN ('x','y','z','etc..'‌​)
, where the values inside of the parentheses are also dynamic at runtime. My service receives a string value that looks like this:
String queryParams.value4 = "x,y,z,etc...";
I can't write the preparedStatement like this: AND column4 IN (?) and then simply plug in queryParams.value4 because it will treat queryParams.value4 as a string literal, which leads to errors.
To solve this issue, I create another arraylist of strings called value4Array. I loop through every character in queryParams.value4, and I check if the current character in the loop is equal to a comma, our delimiter. If it is, then I create a substring of all the characters leading up to that comma, and add that newly created string to value4Array.
The next step is to create the dynamic AND column4 IN (?) statement. I do this by looping through each string value in the value4Array arraylist we just created, and doing a sql.append("?"), based on how many strings are in value4Array. After this, the rest of the logic is the same as my solution above.
/* this function takes the comma delimited string literal (value4 : "x,y,z,etc...")
and parses it into an array of strings. */
private List<String> parseValue4(String value4){
int valueIndex= 0;
List<String> value4Array = new ArrayList<>();
for(int i = 0; i < value4.length(); i++){
if(value4.charAt(i) == ','){
value4Array.add(value4.substring(valueIndex, i));
valueIndex = i + 1;
}
else if(i == value4.length() - 1){
value4Array.add(value4.substring(valueIndex, value4.length()));
}
}
return value4Array;
}
if(someCondition4){
List<String> value4Array = parseValue4(queryParams.value4);
sqlQuery.append("And column4 IN ("); /* base AND statement */
for(int i = 0; i < value4Array.size(); i++){
if(i == value4Array.size() - 1)
sqlQuery.append("?)");
else /* dynamically appending ?'s */
sqlQuery.append("?,");
queryArgs.add(value4Array.get(i));
}
}

You are taking list of string type (List<String> queryArgs = new ArrayList<>();).
What if the parameter is an integer type? Is there any way to store multiple typle of data?

Related

Using List inside a postgres Query

i have a dynamic list.
list=['a','b','c','d' ..... ] so length may change
i want to compare these list values in a query
select * from student where name in(all the list values);
i want to pass list values into this query
how i can do this.. ??? please help
Thank you
In Postgres, you can use arrays. The syntax in the where clause looks like:
where name = any (array[1, 2, 3])
or
where name = any (list_parameter)
You can write a function that gets a list as a parameter and return a string like "'one', 'two','three'".
// need a string like this 'one', 'two'
private String arrayToSqlInChecker(List<String> loc_list) {
StringBuilder value = new StringBuilder("");
for (int i = 0; i < loc_list.size(); i++) {
value.append("'" + loc_list.get(i) + "'");
if (i != loc_list.size() - 1) {
value.append(",");
}
}
return value.toString();
}
And then you have to append this string into you PostgreSQL IN query
"id IN (" + this.arrayToSqlInChecker(loc_list) + ")"
You can also handle null or empty values in the function

DataContext.ExecuteQuery<object> returns object {}

I'm trying to write function for selecting optional columns in linq(columns that may not exist). The problem is in linq like this:
using (DataDataContext db = new DataDataContext()){
var collection = from t in table
select new
{
Nonoptional = t.A;
Optional = IsInDB("table","B") ? t.B : -1; //this is optional column
}}
Unfortunately, this won't work because the fragment near Optional will be translated to case statement and error arises that column not exists.
So i decided to "cover" it with function:
using (DataDataContext db = new DataDataContext()){
var collection = from t in table
select new
{
Nonoptional = t.A;
Optional = IsInDB("table","B") ? OptionalColumnValue<int>("table","B","id_table",t.id_table) : -1; //this is optional column
}}
I want this function to be universal. It should work like that" If there is no value or column is nullable and value is null then return default value for type.
I came up with something like this:
//table,column - obvious,id_column - PK column of table, id - id of currently processing record
public static T OptionalColumnValue<T>(string table,string column,string id_columm,int id) T t = default(T);
DataDataContext db = new DataDataContext();
IEnumerable<object> value = db.ExecuteQuery<object>("select " + column + " from " + table + " where " + id_columm + " = " + id.ToString());
List<object> valueList = value.ToList();
if (valueList.Count == 1)//here is the problem
t = (T)valueList.First();
return t;
}
When there is null value db.ExecuteQuery return something like object{}. I'm assuming this is "empty" object,with nothing really in there. I was thinking about checking for "emptiness" of this object( BTW this is not DBull).
When i realised that this is no way either with concrete value in this column(it cannot cast it to return correct type), then I tried db.ExecuteQuery<T>. Then concrete value - OK, null - Exception.
I thought, maybe Nullable<T> as return value. Nop, because string also can be T.
I don't know what to do next. Maybe there's another solution to this problem.

Write Round in NHibernate criteria

I need to write this in NHibernate Criteria as a projection:
The subAlias is _not_ the root alias, so {alias} cannot replace the correct sql alias, and my problem is that other parts of the query makes the subAlias vary in the generated sql
ROUND(alias.Property / parameterValueFromMethodParameter + ", 0)
* parameterValueFromMethodParameter2 AS SQLAlias
This is how far (off) I got:
.Add(Projections.SqlFunction(new VarArgsSQLFunction("(", "/", ")")
, NHibernateUtil.Int32
, Projections.SqlFunction("round"
, NHibernateUtil.Decimal
, Projections.Property("subAlias.Property"))), "SQLAlias"))
This produces the following SQL code:
ROUND( subAlias3(4).Property
)AS y1_
Does anyone have experience with projections like this?
I found this patch i hibernate, but seems like it was not implemented.
If I understand your example properly, the most easy solution would be to use SQL projection:
// the parameterValueFromMethodParameter
// and parameterValueFromMethodParameter2
var computationParams = new object[] {2, 4}; // just an example numbers
// SQL To be generated
// see that here we work with COLUMN name, not property
var sqlSnippet = " ( ROUND({{alias}}.ColumnName / {0}, 0) * {1} ) AS computed ";
// put that all together
var projectSql = string.Format(sqlSnippet, computationParams);
// IProjection
var projection = Projections.SqlProjection(projectSql, new string[0], new IType[0]);
// add it to SELECT clause
criteria.SetProjection(Projections.ProjectionList()
.Add(projection)
...
);
That should work...
I Solved it by writing my own SQL IProjection. With a litte help from this example.
public SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
{
string replacedString = Regex.Replace(
this.sql,
#"{([a-zA-Z_]((\.)?[a-zA-Z0-9_])*)}",
m =>
{
ICriteria critter = criteria.GetCriteriaByAlias(m.Groups[1].Value);
if (critter != null)
{
return criteriaQuery.GetSQLAlias(critter);
}
return m.Groups[0].Value;
});
return new SqlString(replacedString);
}
So now I can do (In a SqlGroupProjection):
ROUND({subAlias}.XCoord / " + aggregationSize + ", 0) * " + aggregationSize + " AS SQLAlias
There are some other attempts on extending (N)Hibernate to handle this alias in raw SQL problem:
Expression.Sql should support aliases other than {alias}
Support for referencing non-root entities in Criteria SQL expressions

String is to Substring, as ArrayList is to?

In Java, and many other languages, one can grab a subsection of a string by saying something like String.substring(begin, end). My question is, Does there exist a built-in capability to do the same with Lists in Java that returns a sublist from the original?
This method is called subList and exists for both array and linked lists. Beware that the list it returns is backed by the existing list so updating the original one will update the slice.
The answer can be found in the List API: List#subList(int, int) (can't figure out how to get the link working....)
Be warned, though, that this is a view of the underlying list, so if you change the original list, you'll change the sublist, and the semantics of the sublist is undefined if you structurally modify the original list. So I suppose it isn't strictly what you're looking for...
If you want a structurally independent subsection of the list, I believe you'll have to do something like:
ArrayList<something> copy = new ArrayList<>(oldList.subsection(begin, end));
However, this will retain references to the original objects in the sublist. You'll probably have to manually clone everything if you want a completely new list.
The method is called sublist and can be found here in the javadocs
http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html#subList(int, int)
You can use subList(start, end)
ArrayList<String> arrl = new ArrayList<String>();
//adding elements to the end
arrl.add("First");
arrl.add("Second");
arrl.add("Third");
arrl.add("Random");
arrl.add("Click");
System.out.println("Actual ArrayList:"+arrl);
List<String> list = arrl.subList(2, 4);
System.out.println("Sub List: "+list);
Ouput :
Actual ArrayList:[First, Second, Third, Random, Click]
Sub List: [Third, Random]
You might just want to make a new method if you want it to be exactly like substring is to String.
public static List<String> sub(List<String> strs, int start, int end) {
List<String> ret = new ArrayList<>(); //Make a new empty ArrayList with String values
for (int i = start; i < end; i++) { //From start inclusive to end exclusive
ret.add(strs.get(i)); //Append the value of strs at the current index to the end of ret
}
return ret;
}
public static List<String> sub(List<String> strs, int start) {
List<String> ret = new ArrayList<>(); //Make a new empty ArrayList with String values
for (int i = start; i < strs.size(); i++) { //From start inclusive to the end of strs
ret.add(strs.get(i)); //Append the value of strs at the current index to the end of ret
}
return ret;
}
If myStrings is an ArrayList of the following Strings: {"do","you","really","think","I","am","addicted","to","coding"}, then sub(myStrings,1,6) would return {"you", "really", "think", "I", "am"} and sub(myStrings,4) would return {"I", "am", "addicted", "to", "coding"}. Also by doing sub(myStrings, 0) it would rewrite myStrings as a new ArrayList which could help with referencing problems.

Does Dapper support the like operator?

Using Dapper-dot-net...
The following yields no results in the data object:
var data = conn.Query(#"
select top 25
Term as Label,
Type,
ID
from SearchTerms
WHERE Term like '%#T%'",
new { T = (string)term });
However, when I just use a regular String Format like:
string QueryString = String.Format("select top 25 Term as Label, Type, ID from SearchTerms WHERE Term like '%{0}%'", term);
var data = conn.Query(QueryString);
I get 25 rows back in the collection. Is Dapper not correctly parsing the end of the parameter #T?
Try:
term = "whateverterm";
var encodeForLike = term => term.Replace("[", "[[]").Replace("%", "[%]");
string term = "%" + encodeForLike(term) + "%";
var data = conn.Query(#"
select top 25
Term as Label,
Type,
ID
from SearchTerms
WHERE Term like #term",
new { term });
There is nothing special about like operators, you never want your params inside string literals, they will not work, instead they will be interpreted as a string.
note
The hard-coded example in your second snippet is strongly discouraged, besides being a huge problem with sql injection, it can cause dapper to leak.
caveat
Any like match that is leading with a wildcard is not SARGable, which means it is slow and will require an index scan.
Yes it does. This simple solution has worked for me everytime:
db.Query<Remitente>("SELECT *
FROM Remitentes
WHERE Nombre LIKE #n", new { n = "%" + nombre + "%" })
.ToList();
Best way to use this to add concat function in query as it save in sql injecting as well, but concat function is only support above than sql 2012
string query = "SELECT * from country WHERE Name LIKE CONCAT('%',#name,'%');"
var results = connection.query<country>(query, new {name});
The answer from Sam wasn't working for me so after some testing I came up with using the SQLite CONCAT equivalent which seems to work:
string sql = "SELECT * FROM myTable WHERE Name LIKE '%' || #NAME || '%'";
var data = IEnumerable data = conn.Query(sql, new { NAME = Name });
Just to digress on Sam's answer, here is how I created two helper methods to make searches a bit easier using the LIKE operator.
First, creating a method for generating a parameterized query, this method uses dynamic: , but creating a strongly typed generic method should be more desired in many cases where you want static typing instead of dynamic.
public static dynamic ParameterizedQuery(this IDbConnection connection, string sql, Dictionary<string, object> parametersDictionary)
{
if (string.IsNullOrEmpty(sql))
{
return null;
}
string missingParameters = string.Empty;
foreach (var item in parametersDictionary)
{
if (!sql.Contains(item.Key))
{
missingParameters += $"Missing parameter: {item.Key}";
}
}
if (!string.IsNullOrEmpty(missingParameters))
{
throw new ArgumentException($"Parameterized query failed. {missingParameters}");
}
var parameters = new DynamicParameters(parametersDictionary);
return connection.Query(sql, parameters);
}
Then adding a method to create a Like search term that will work with Dapper.
public static string Like(string searchTerm)
{
if (string.IsNullOrEmpty(searchTerm))
{
return null;
}
Func<string, string> encodeForLike = searchTerm => searchTerm.Replace("[", "[[]").Replace("%", "[%]");
return $"%{encodeForLike(searchTerm)}%";
}
Example usage:
var sql = $"select * from products where ProductName like #ProdName";
var herringsInNorthwindDb = connection.ParameterizedQuery(sql, new Dictionary<string, object> { { "#ProdName", Like("sild") } });
foreach (var herring in herringsInNorthwindDb)
{
Console.WriteLine($"{herring.ProductName}");
}
And we get our sample data from Northwind DB:
I like this approach, since we get helper extension methods to do repetitive work.
My solution simple to this problem :
parameter.Add("#nomeCliente", dfNomeCliPesquisa.Text.ToUpper());
query = "SELECT * FROM cadastrocliente WHERE upper(nome) LIKE " + "'%" + dfNomeCliPesquisa.Text.ToUpper() + "%'";