reuse sql param when not using named parameters - java

I have a query that gets called with JdbcTemplate but only one param is getting sent and I need to use that one param in two where conditions.
The Query
String sql = "select * from employee where salary > ? and netpay > ?";
The Call
The param here is only one. I.E. if the id is TEST123 the query needs to be
select * from employee where id = TEST123 and name = TEST123 even though one param is getting passed.
getJdbcTemplate().query(sql, new Object[]{"TEST123"}, CustomResultSetExtractor());
Is there any way to do this from the query side instead of passing two params?
NOTE
I do not have access to change the way the query is called, hence I cannot add named params, or just pass an additional parameter.

Use NamedParameterJdbcTemplate, a JdbcTemplate wrapper:
Template class with a basic set of JDBC operations, allowing the use of named parameters rather than traditional '?' placeholders.
This class delegates to a wrapped JdbcTemplate once the substitution from named parameters to JDBC style '?' placeholders is done at execution time.
Your SQL will be with 1 parameter:
select * from employee where id = (:id) and name = (:id)
And code will be :
MapSqlParameterSource args = new MapSqlParameterSource();
args.addValue("id", TEST123);
return new NamedParameterJdbcTemplate(getJdbcTemplate()).query(sql , args, youRowMapper);
If you can't change it, you can change your query to:
select * from employee where id = ? and id = name

I am amazed that you didn't find:
String sql = "select * from employee where id = ? and name = id";
Or did you mean or instead of and?
String sql = "select * from employee where ? in (id, name)";

I would suggest something else, you can repeat your param in Object array for example if your query have two ? then generate the parameters like so :
String param = "TEST123";
Object[] params = IntStream.range(0, 2)
.mapToObj(i -> param)
.toArray();
This will generate an array which hold two time TEST123 :
[TEST123, TEST123]
Then Just send the Object array to your code like you do.
getJdbcTemplate().query(sql, params, CustomResultSetExtractor());
If you don't know the number of hold or parameters in your query you can count them like so :
int numberOfHold = sql.length() - sql.replaceAll("\\?", "").length();

Related

Vertx Mysql Client prepareQuery with in statement

I am trying to execute query with vertx with in sql statement and I am failing to make it work. I don't understand how should I pass a collection of values
here is what I am trying to do:
MySQLConnectOptions connectOptions = new MySQLConnectOptions()
.setPort(3306)
.setHost("localhost")
.setDatabase("innodb")
.setUser("root")
.setPassword("local1234");
MySQLPool sqlPool = MySQLPool.pool(connectOptions, new PoolOptions());
PreparedQuery<RowSet<Row>> prepare = sqlPool.preparedQuery(
"select * from myTable where someId in (?)");
List<String> ids = List.of("someUniqueId", "other");
Future<RowSet<Row>> execute = prepare.execute(Tuple.of(ids));
execute.onComplete(
rows -> System.out.println(rows.result().size())
);
When running with one value without the in (?) it is working fine
any ideas?
These two queries are different:
select * from myTable where someId in ('123','456','789')
select * from myTable where someId in ('123,456,789')
The first query has a list of three discrete values. The second query has one value, a string that happens to contain commas.
A parameter can only be used to substitute for a scalar value. So when you pass a string with commas as the value, it is as if you executed the second query I showed.
To get the result you want, you need multiple parameter placeholders:
select * from myTable where someId in (?, ?, ?)
Use as many placeholders as the number of elements in the tuple.

Can we pass values present as hashmap in method parameter to sql where clause in repository method

For example i have values like month, year, frequency in Map and i want to use these values in where clause only if they are not null if value of any variable is null then we won't include that in where clause. I have written below query which is working but i am not sure if we can do this dynamically like is ther any if condition we can use with where clause.
#Query(value = "SELECT * FROM job where synthetic_id is not null and "MONTH(timer) = :month and YEAR(timer) = :year", nativeQuery = true)
List<JobEntity> getAllModelSchedulesByMonth(Map<String, Object> parametersBaseOn);
Thanks in advance.

How to query for a single column in NamedJdbcTemplate?

How do I query for a List of Integers using namedParameterJdbcTemplate? I tried following this template, it is not working below .
https://stackoverflow.com/a/40417349/15435022
String customerName = "JOE";
MapSqlParameterSource customerParameters = new MapSqlParameterSource();
customerParameters.addValue("CustomerName", customerName);
private static final String query = "SELECT Customer_Id From dbo.Customers WHERE Customer_Name = :CustomerName";
List<Integer> data = namedParameterJdbcTemplate.queryForList(query, Integer.class);
Error: Cannot resolve method 'queryForList(java.lang.String, java.lang.Class<java.lang.Integer>)'
Why code gives an error
As mentioned in the docs queryForList Method have following implementations available:
queryForList(String sql, Map<String,?> paramMap).
queryForList(String sql, Map<String,?> paramMap, Class<T> elementType)
queryForList(String sql, SqlParameterSource paramSource)
queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)
None of these implementations matches the parameters used in the given implementation. Thus, we end up with this error:
Error: Cannot resolve method 'queryForList(java.lang.String, java.lang.Class<java.lang.Integer>)'
The idea behind passing missing parameter
The key idea behind passing a Map or ParameterSource is to have a dynamic query where we can put in values later on.
Eg:
String query = "Select :columnName from table";
Map<String,String> map = new HashMap<>();
map.put("columnName", "userName");
When this map is passed along with the query String, internally it is used to replace placeholders with the values from the map.
How to fix the code
There are two ways you can fix this:
Just pass null
This is not the best way of fixing the problem is definitely not recommended for a production code. But, this can be used if there is no placeholder in the query string.
Code:
List<Integer> data = namedParameterJdbcTemplate.queryForList(query, null, Integer.class);
Create and pass an empty Map or SqlParameterSource
You already have a MapSqlParameterSource called customerParameters in your code. Simply pass it while calling queryForList()
Code:
List<Integer> data = namedParameterJdbcTemplate.queryForList(query, customerParameters, Integer.class);

How to assign a value to the MySQL statement with the in keyword in Java?

The parameters to be passed are the int type and the quantity is uncertain.
How can I pass all the parameters at once in the case of uncertain parameters?
String SQL = "select * from table where enterprise_id in (?,?,?)";
int a,b,c = 1,2,3;//uncertain quantity
this.server.findBySql(SQL,a,b,c);
Is there a good way to avoid traversing parameters and splicing the query statements?
I think the easiest way is to pass a list is to use
org.springframework.jdbc.core.namedparam.MapSqlParameterSource.MapSqlParameterSource() which can take any type of argument for a prepared statement.
So, in your case, you can modify your SQL like this to take list parameter:
String sql = "select * from table where enterprise_id in (:listOfInt)";.
Then add the list as parameter:
MapSqlParameterSource sqlParams = new MapSqlParameterSource();
sqlParams.addValue("listOfInt", Arrays.asList(1,2,3));
Pass it to the org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate instance to execute the query like this,
this.namedParameterJdbcTemplate.queryForList(sql, sqlParams);
Which gives you a resultset, further this assumes that you have created an instance of NamedParameterJdbcTemplate at the class level.
I'am not a Java dev, but you shall pass an array parameter to an IN SQL statement:
String SQL = "select * from table where enterprise_id in (?)";
int[] array = {1, 2, 3};//uncertain quantity
this.server.findBySql(SQL, array);
please see How to use an arraylist as a prepared statement parameter for the right way to do this.
Be carefull, if your array may be very large (I mean, very very large), you better have to insert it into a temporary table using a bulk insert and then to use a JOIN statement (but only if you may reach the MySQL IN limits).
We can definitely pass array of type int to stored procs, but not sure about statements.
However we can get away with workaround like this.
Let's say int values are coming from int[] arrData.
StringBuffer arguments= new StringBuffer();
for(Integer value:arrData){
if(arguments.toString().length!=0)
arguments.append(",");
arguments.append(""+value+"");
}
and then finally pass comma separated values as input like this.
String SQL = "select * from table where enterprise_id in (?)";
this.server.findBySql(SQL,arguments.toString());

createNativeQuery will not return rows in java but will in db2 sql [duplicate]

I know I can pass a list to named query in JPA, but how about NamedNativeQuery? I have tried many ways but still can't just pass the list to a NamedNativeQuery. Anyone know how to pass a list to the in clause in NamedNativeQuery? Thank you very much!
The NamedNativeQuery is as below:
#NamedNativeQuery(
name="User.findByUserIdList",
query="select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in (?userIdList)"
)
and it is called like this:
List<Object[]> userList = em.createNamedQuery("User.findByUserIdList").setParameter("userIdList", list).getResultList();
However the result is not as I expected.
System.out.println(userList.size()); //output 1
Object[] user = userList.get(0);
System.out.println(user.length); //expected 5 but result is 3
System.out.println(user[0]); //output MDAVERSION which is not a user_id
System.out.println(user[1]); //output 5
System.out.println(user[2]); //output 7
The above accepted answer is not correct and led me off track for many days !!
JPA and Hibernate both accept collections in native query using Query.
You just need to do
String nativeQuery = "Select * from A where name in :names"; //use (:names) for older versions of hibernate
Query q = em.createNativeQuery(nativeQuery);
q.setParameter("names", l);
Also refer the answers here which suggest the same (I picked the above example from one of them)
Reference 1
Reference 2 which mentioned which cases paranthesis works which giving the list as a parameter
*note that these references are about jpql queries, nevertheless the usage of collections is working with native queries too.
A list is not a valid parameter for a native SQL query, as it cannot be bound in JDBC. You need to have a parameter for each argument in the list.
where u.user_id in (?id1, ?id2)
This is supported through JPQL, but not SQL, so you could use JPQL instead of a native query.
Some JPA providers may support this, so you may want to log a bug with your provider.
Depending on your database/provider/driver/etc., you can, in fact, pass a list in as a bound parameter to a JPA native query.
For example, with Postgres and EclipseLink, the following works (returning true), demonstrating multidimensional arrays and how to get an array of double precision. (Do SELECT pg_type.* FROM pg_catalog.pg_type for other types; probably the ones with _, but strip it off before using it.)
Array test = entityManager.unwrap(Connection.class).createArrayOf("float8", new Double[][] { { 1.0, 2.5 }, { 4.1, 5.0 } });
Object result = entityManager.createNativeQuery("SELECT ARRAY[[CAST(1.0 as double precision), 2.5],[4.1, 5.0]] = ?").setParameter(1, test).getSingleResult();
The cast is there so the literal array is of doubles rather than numeric.
More to the point of the question - I don't know how or if you can do named queries; I think it depends, maybe. But I think following would work for the Array stuff.
Array list = entityManager.unwrap(Connection.class).createArrayOf("int8", arrayOfUserIds);
List<Object[]> userList = entityManager.createNativeQuery("select u.* from user u "+
"where u.user_id = ANY(?)")
.setParameter(1, list)
.getResultList();
I don't have the same schema as OP, so I haven't checked this exactly, but I think it should work - again, at least on Postgres & EclipseLink.
Also, the key was found in: http://tonaconsulting.com/postgres-and-multi-dimensions-arrays-in-jdbc/
Using hibernate, JPA 2.1 and deltaspike data I could pass a list as parameter in query that contains IN clause. my query is below.
#Query(value = "SELECT DISTINCT r.* FROM EVENT AS r JOIN EVENT AS t on r.COR_UUID = t.COR_UUID where " +
"r.eventType='Creation' and t.eventType = 'Reception' and r.EVENT_UUID in ?1", isNative = true)
public List<EventT> findDeliveredCreatedEvents(List<String> eventIds);
can be as simple as:
#Query(nativeQuery =true,value = "SELECT * FROM Employee as e WHERE e.employeeName IN (:names)")
List<Employee> findByEmployeeName(#Param("names") List<String> names);
currently I use JPA 2.1 with Hibernate
I also use IN condition with native query. Example of my query
SELECT ... WHERE table_name.id IN (?1)
I noticed that it's impossible to pass String like "id_1, id_2, id_3" because of limitations described by James
But when you use jpa 2.1 + hibernate it's possible to pass List of string values. For my case next code is valid:
List<String> idList = new ArrayList<>();
idList.add("344710");
idList.add("574477");
idList.add("508290");
query.setParameter(1, idList);
In my case ( EclipseLink , PostGreSQL ) this works :
ServerSession serverSession = this.entityManager.unwrap(ServerSession.class);
Accessor accessor = serverSession.getAccessor();
accessor.reestablishConnection(serverSession);
BigDecimal result;
try {
Array jiraIssues = accessor.getConnection().createArrayOf("numeric", mandayWorkLogQueryModel.getJiraIssues().toArray());
Query nativeQuery = this.entityManager.createNativeQuery(projectMandayWorkLogQueryProvider.provide(mandayWorkLogQueryModel));
nativeQuery.setParameter(1,mandayWorkLogQueryModel.getPsymbol());
nativeQuery.setParameter(2,jiraIssues);
nativeQuery.setParameter(3,mandayWorkLogQueryModel.getFrom());
nativeQuery.setParameter(4,mandayWorkLogQueryModel.getTo());
result = (BigDecimal) nativeQuery.getSingleResult();
} catch (Exception e) {
throw new DataAccessException(e);
}
return result;
Also in query cannot use IN(?) because you will get error like :
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: numeric = numeric[]
'IN(?)' must be swapped to '= ANY(?)'
My solution was based on Erhannis concept.
In jpa, it worked for me
#Query(nativeQuery =true,value = "SELECT * FROM Employee as e WHERE e.employeeName IN (:names)")
List<Employee> findByEmployeeName(#Param("names") List<String> names);
Tried in JPA2 with Hibernate as provider and it seems hibernate does support taking in a list for "IN" and it works. (At least for named queries and I believe it will be similar with named NATIVE queries)
What hibernate does internally is generate dynamic parameters, inside the IN same as the number of elements in the passed in list.
So in you example above
List<Object[]> userList = em.createNamedQuery("User.findByUserIdList").setParameter("userIdList", list).getResultList();
If list has 2 elements the query will look like
select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in (?, ?)
and if it has 3 elements it looks like
select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in (?, ?, ?)
you should do this:
String userIds ="1,2,3,4,5";
List<String> userIdList= Stream.of(userIds.split(",")).collect(Collectors.toList());
Then, passes like parameter inside your query, like this:
#NamedNativeQuery(name="User.findByUserIdList", query="select u.user_id, u.dob, u.name, u.sex, u.address from user u where u.user_id in (?userIdList)")
It's not possible with standard JPA. Hibernate offers the proprietary method setParameterList(), but it only works with Hibernate sessions and is not available in JPA's EntityManager.
I came up with the following workaround for Hibernate, which is not ideal but almost standard JPA code and has some nice properties to it.
For starters you can keep the named native query nicely separated in a orm.xml file:
<named-native-query name="Item.FIND_BY_COLORS" result-class="com.example.Item">
<query>
SELECT i.*
FROM item i
WHERE i.color IN ('blue',':colors')
AND i.shape = :shape
</query>
</named-native-query>
The placeholder is wrapped in single quotes, so it's a valid native JPA query. It runs without setting a parameter list and would still return correct results when other matching color parameters are set around it.
Set the parameter list in your DAO or repository class:
#SuppressWarnings("unchecked")
public List<Item> findByColors(List<String> colors) {
String sql = getQueryString(Item.FIND_BY_COLORS, Item.class);
sql = setParameterList(sql, "colors", colors);
return entityManager
.createNativeQuery(sql, Item.class)
.setParameter("shape", 'BOX')
.getResultList();
}
No manual construction of query strings. You can set any other parameter as you normally would.
Helper methods:
String setParameterList(String sql, String name, Collection<String> values) {
return sql.replaceFirst(":" + name, String.join("','", values));
}
String getQueryString(String queryName, Class<?> resultClass) {
return entityManager
.createNamedQuery(queryName, resultClass)
.unwrap(org.hibernate.query.Query.class) // Provider specific
.getQueryString();
}
So basically we're reading a query string from orm.xml, manually set a parameter list and then create the native JPA query. Unfortunately, createNativeQuery().getResultList() returns an untyped query and untyped list even though we passed a result class to it. Hence the #SuppressWarnings("unchecked").
Downside: Unwrapping a query without executing it may be more complicated or impossible for JPA providers other than Hibernate. For example, the following might work for EclipseLink (untested, taken from Can I get the SQL string from a JPA query object?):
Session session = em.unwrap(JpaEntityManager.class).getActiveSession();
DatabaseQuery databaseQuery = query.unwrap(EJBQueryImpl.class).getDatabaseQuery();
databaseQuery.prepareCall(session, new DatabaseRecord());
Record r = databaseQuery.getTranslationRow();
String bound = databaseQuery.getTranslatedSQLString(session, r);
String sqlString = databaseQuery.getSQLString();
An alternative might be to store the query in a text file and add code to read it from there.
You can pass in a list as a parameter, but:
if you create a #NamedNativeQuery and use .createNamedQuery(), you don't use named param, you used ?1(positional parameter). It starts with 1, not 0.
if you use .createNativeQuery(String), you can use named param.
You can try this :userIdList instead of (?userIdList)
#NamedNativeQuery(
name="User.findByUserIdList",
query="select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in :userIdList"
)

Categories