What are the best workarounds for using a SQL IN
clause with instances of java.sql.PreparedStatement
, which is not supported for multiple values du
Generate the query string in the PreparedStatement to have a number of ?'s matching the number of items in your list. Here's an example:
public void myQuery(List<String> items, int other) {
...
String q4in = generateQsForIn(items.size());
String sql = "select * from stuff where foo in ( " + q4in + " ) and bar = ?";
PreparedStatement ps = connection.prepareStatement(sql);
int i = 1;
for (String item : items) {
ps.setString(i++, item);
}
ps.setInt(i++, other);
ResultSet rs = ps.executeQuery();
...
}
private String generateQsForIn(int numQs) {
String items = "";
for (int i = 0; i < numQs; i++) {
if (i != 0) items += ", ";
items += "?";
}
return items;
}
Just for completeness and because I did not see anyone else suggest it:
Before implementing any of the complicated suggestions above consider if SQL injection is indeed a problem in your scenario.
In many cases the value provided to IN (...) is a list of ids that have been generated in a way that you can be sure that no injection is possible... (e.g. the results of a previous select some_id from some_table where some_condition.)
If that is the case you might just concatenate this value and not use the services or the prepared statement for it or use them for other parameters of this query.
query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
My example for SQLite and Oracle databases.
The first For loop is for Creating a PreparedStatement Objects.
The second For loop is for Supplying Values for PreparedStatement Parameters.
List<String> roles = Arrays.asList("role1","role2","role3");
Map<String, String> menu = getMenu(conn, roles);
public static Map<String, String> getMenu(Connection conn, List<String> roles ) {
Map<String, String> menu = new LinkedHashMap<String, String>();
PreparedStatement stmt;
ResultSet rset;
String sql;
try {
if (roles == null) {
throw new Exception();
}
int size = roles.size();
if (size == 0) {
throw new Exception("empty list");
}
StringBuilder sb = new StringBuilder();
sb.append("select page_controller, page_name from pages "
+ " where page_controller in (");
for (int i = 0; i < size; i++) {
sb.append("?,");
}
sb.setLength(sb.length() - 1);
sb.append(") order by page_id");
sql = sb.toString();
stmt = conn.prepareStatement(sql);
for (int i = 0; i < size; i++) {
stmt.setString(i + 1, roles.get(i));
}
rset = stmt.executeQuery();
while (rset.next()) {
menu.put(rset.getString(1), rset.getString(2));
}
conn.close();
} catch (Exception ex) {
logger.info(ex.toString());
try {
conn.close();
} catch (SQLException e) {
}
return menu;
}
return menu;
}
instead of using
SELECT my_column FROM my_table where search_column IN (?)
use the Sql Statement as
select id, name from users where id in (?, ?, ?)
and
preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');
or use a stored procedure this would be the best solution, since the sql statements will be compiled and stored in DataBase server
You can use Collections.nCopies
to generate a collection of placeholders and join them using String.join
:
List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try ( Connection connection = getConnection();
PreparedStatement ps = connection.prepareStatement(sql)) {
int i = 1;
for (String param : params) {
ps.setString(i++, param);
}
/*
* Execute query/do stuff
*/
}
Sormula supports SQL IN operator by allowing you to supply a java.util.Collection object as a parameter. It creates a prepared statement with a ? for each of the elements the collection. See Example 4 (SQL in example is a comment to clarify what is created but is not used by Sormula).