Parameterized IN clause using multiple columns - java

I have a query along these lines, where I am trying to filter the result set by comparing tuples (like SQL multiple columns in IN clause):
select *
from mytable
where (key, value) in (values
('key1', 'value1'),
('key2', 'value2'),
...
);
This is valid syntax and works fine on my PostgreSQL 9.3 database.
I want to invoke this query through Spring JDBC where the in value pairs come from a List<Map<String, String>>.
It would be nice to do something like this:
List<Map<String, String>> valuesMap = ...;
String sql = "select * from mytable where (key, value) in (values :valuesMap)";
SqlParameterSource params = new MapSqlParameterSource("valuesMap", valuesMap);
jdbcTemplate.query(sql, params, rowMapper);
When I try this, I get:
org.postgresql.util.PSQLException: No hstore extension installed.
at org.postgresql.jdbc2.AbstractJdbc2Statement.setMap(AbstractJdbc2Statement.java:1707) ~[postgresql-9.3-1101-jdbc41.jar:na]
at org.postgresql.jdbc2.AbstractJdbc2Statement.setObject(AbstractJdbc2Statement.java:1910) ~[postgresql-9.3-1101-jdbc41.jar:na]
at org.postgresql.jdbc3g.AbstractJdbc3gStatement.setObject(AbstractJdbc3gStatement.java:36) ~[postgresql-9.3-1101-jdbc41.jar:na]
at org.postgresql.jdbc4.AbstractJdbc4Statement.setObject(AbstractJdbc4Statement.java:47) ~[postgresql-9.3-1101-jdbc41.jar:na]
at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:427) ~[spring-jdbc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:235) ~[spring-jdbc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:150) ~[spring-jdbc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.setValues(PreparedStatementCreatorFactory.java:287) ~[spring-jdbc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.createPreparedStatement(PreparedStatementCreatorFactory.java:244) ~[spring-jdbc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:623) ~[spring-jdbc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
I've looked at the the hstore extension it mentions. It doesn't seem relevant to my problem.
Is there a way to accomplish this without dynamically building the SQL and parameter list?

All you have to do is to pass a list of arrays, where each array contains a key and value, like this:
HashMap<String , String > map = new HashMap<>();
map.put("key0", "value0");
map.put("key1", "value1");
Set<String> keys = map.keySet();
List<String[]> valuesMap = new ArrayList<>();
for(String key:keys){
String[] entry = {key,map.get(key)};
valuesMap.add(entry);
}
String sql = "select * from mytable where (key, value) in (values :valuesMap)";
SqlParameterSource params = new MapSqlParameterSource("valuesMap", valuesMap);
jdbcTemplate.query(sql, params, rowMapper);
This is mentioned in the Spring documentation: http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/jdbc.html#jdbc-in-clause

Unfortunately, there isn't any easy way to bind a nested collection bind variable to PostgreSQL. You could generate the following SQL string instead
SELECT *
FROM mytable
WHERE (key, value) IN (
(?, ?),
(?, ?),
...
);
That's a bit of work to keep the SQL string and the variable bindings in sync.
You could, however, encode the map as JSON as such:
SELECT *
FROM mytable
WHERE (key, value) IN (
SELECT
t->>'key',
t->>'value'
FROM json_array_elements(?) AS t(v)
)
E.g.
SELECT *
FROM mytable
WHERE (key, value) IN (
SELECT
t->>'key',
t->>'value'
FROM json_array_elements(
'[{"key":"key1","value":"value1"},
{"key":"key2","value":"value2"}]'
) AS t(v)
)
In that case, you would only ever need a single VARCHAR bind variable

If you can't get your solution to work, you could also just concatenate the key and value.
Perhaps JDBC has less problems with this more basic syntax:
select *
from mytable
where (key||value) in (
('key1value1'),
('key2value2'),
...
);
For this to work, you'd need to first convert your Java Map to a List with the key and values concatenated as well.

It might not be an issue with the query, it might be that you don't have any hstore created/installed.
I suggest the following steps to debug your problem:
Try a very simple query, without any parameters.
If you get the same issue, check how to create extensions: http://www.postgresql.org/docs/9.1/static/sql-createextension.html
Otherwise, if the query executed correctly, try to use a simple parameter (with =?)
Finally, try named queries. Something like:
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(namedSql);
List<Integer> parameters = new ArrayList<Integer>();
for (A a : paramBeans)
parameters.add(a.getId());
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
parameterSource.addValue("placeholder1, parameters);
// create SQL with ?'s
String sql = NamedParameterUtils.substituteNamedParameters(parsedSql, parameterSource);
return sql;
Also check this discussion, I find it useful: How to execute IN() SQL queries with Spring's JDBCTemplate effectivly?

Related

Supply a list of column values with wildcards, to a sql query

Using Java 8 , Oracle 11g and Spring Boot, we normally run a SQL select something like:
String sqlQuery="select col1,col2 from myTable where col1 = :colValue";
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("colValue", "xxx");
// Then run the query :
List<MyClass>= jdbcTemplate.query(sqlQuery,parameters,(rs, rowNum) -> new MyClass
(rs.getString("col1"),rs.getString("col2")) );
It is also possible to supply a List of col1 values
String sqlQuery="select col1,col2 from myTable where col1 in ( :colList) ";
Map<String, List<String>> parameters = new HashMap<String, List<String>();
List<String> myColList= Arrays.asList("xxx", "yyy");
parameters.put("colList", myColList );
But what if we want to include SQL wildcards in the column values:
String sqlQuery="select col1,col2 from myTable where col1 like ( :colList) ";
List<String> myColList= Arrays.asList("%xxx%", "yyy%");
We can't mix LIKE and IN constructs in a where clauses, so is there any other way of doing it? I know I can dynamically construct my own where clause but I'm trying to avoid that. Any pointers appreciated.
You can use MapSqlParameterSource
Set<Integer> myColList = new HashSet<>(Arrays.asList("xxx", "yyy"));
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("colList", myColList);

BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar, when I give a timestamp argument

In my Java application, I used to have a sql query such as below :
INSERT INTO "KPI_MEASURE" (
id,
created_at,
kpi_project_id,
kpi_frequency_id,
kpi_metric_id,
branch,
value
)
SELECT
nextval('"KPI_MEASURE_ID_seq"'::regclass),
now(),
kpi_measure.kpi_project_id,
kpi_measure.kpi_frequency_id,
kpi_measure.kpi_metric_id ,
kpi_measure.branch ,
sum(kpi_measure.value)
FROM "KPI_MEASURE" kpi_measure
INNER JOIN "KPI_METRIC" kpi_metric ON kpi_measure.kpi_metric_id = kpi_metric.id
INNER JOIN "KPI_PROJECT" kpi_project ON kpi_measure.kpi_project_id = kpi_project.id
INNER JOIN "KPI_AGGREGATION_PROJECT" kpi_agg_project ON kpi_project.name = kpi_agg_project.child_project_name
WHERE kpi_metric.aggregated = false
GROUP BY kpi_measure.branch, kpi_measure.kpi_metric_id, kpi_measure.kpi_project_id, kpi_project.name, kpi_measure.kpi_frequency_id;
I executed that sql, with jdbcTemplate.update, and it worked.
But recently I changed the value now() by an argument: :today_date, I give that argument with my code:
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(TODAY_DATE, today); // TODAY_DATE = today_date
jdbcTemplate.update(sql, parameters);
But now, it creates an error:
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar
...nested exception is org.postgresql.util.PSQLException: No hstore extension installed.
I don't see why it's an error, since it's what I was told to do, when we want to add argument with the jdbc query.
Edit:
The column data type is timestamp without time zone, while the today variable is of String type.
I guess, I should have used Timestamp, so I changed my code:
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(TODAY_DATE, new Timestamp(System.currentTimeMillis())); // TODAY_DATE = today_date
jdbcTemplate.update(sql, parameters);
Now I only have the the error
"PSQLException: No hstore extension installed."
Maybe, I need to add an hstore, but I can't seem to understand why.
You are using JdbcTenmplate with named parameters, but JdbcTemplate supports only positioned parameters (?). Replace JdbcTemplate with NamedParameterJdbcTemplate or rewrite your query as:
INSERT INTO "KPI_MEASURE" (
id,
created_at,
kpi_project_id,
kpi_frequency_id,
kpi_metric_id,
branch,
value
)
SELECT
nextval('"KPI_MEASURE_ID_seq"'::regclass),
?,
kpi_measure.kpi_project_id,
kpi_measure.kpi_frequency_id,
kpi_measure.kpi_metric_id ,
kpi_measure.branch ,
sum(kpi_measure.value)
FROM "KPI_MEASURE" kpi_measure
INNER JOIN "KPI_METRIC" kpi_metric ON kpi_measure.kpi_metric_id = kpi_metric.id
INNER JOIN "KPI_PROJECT" kpi_project ON kpi_measure.kpi_project_id = kpi_project.id
INNER JOIN "KPI_AGGREGATION_PROJECT" kpi_agg_project ON kpi_project.name = kpi_agg_project.child_project_name
WHERE kpi_metric.aggregated = false
GROUP BY kpi_measure.branch, kpi_measure.kpi_metric_id, kpi_measure.kpi_project_id, kpi_project.name, kpi_measure.kpi_frequency_id;
and call jdbcTemplate.update(sql, new Timestamp(System.currentTimeMillis()));

How to send List of dates as part of sql query using SpringTemplate + JDBCTemplate

Well, I have a query like this :
SELECT * FROM table WHERE someDate IN (date1, date2, date3);
I am trying to construct this using ST (SpringTemplate) and am using JDBCTemplate to make the query.
If I have to pass only one date, I can use :
stringTemplateInstance.add("someColKey",dateInstance);
But how can I send a list of dates so that the IN clause gets it?.
Right now, I am using StringUtils.collectionToCommaDelimitedString(dateListForQuery); to the query ( which I don't like).
Ditch both the StringTemplate as well as the JdbcTemplate and switch to the NamedParameterJdbcTemplate.
String query = "select * from table where someDate in (:dates)";
Map<String, Object> params = new HashMap<String, Object>();
params.put("dates", yourlistofdates);
List<YourResultType> result = template.query(query, params, new YourResultTypeRowMapper());
That is all.

Multiple parameters to SQL JdbcTemplate

I have query like
`Select * from Table1 where xyz in (List of String to be Supplied).
In my java code. I have a dao object in which I am calling this sql using jdbc template.
The method takes in a list of String and that needs to be supplied to this SQl. I have my row-mapper.
How to write the SQl and how to pass the list of variables?
My SQL will run on a Teradata Db.
Use a NamedParameterJdbcTemplate which, as the doc says:
It also allows for expanding a List of values to the appropriate number of placeholders.
So you just need
String sql = "select * from Table1 where xyz in :list";
// or String sql = "select * from Table1 where xyz in (:list)";
// I can't remember which one is right
Map parameters = new HashMap<String, Object>();
parameters.put("list", theListOfXyz);
List<Foo> result = template.query(sql, parameters, rowMapper);

Order by attribute of foreign entity in ORMLite

How can I build a query in ORMLite so that I can use the orderBy function (using either the one with the raw string or the parametrized one) referencing an attribute of a different entity than the one of the dao I'm building the query from? My query is built like that:
// Inner query for performances
QueryBuilder<Performance, String> performancesQB = performanceDao.queryBuilder();
performancesQB.selectColumns("performance_id");
SelectArg performanceSelectArg = new SelectArg();
performancesQB.where().lt("date", performanceSelectArg);
// Outer query for Order objects, where the id matches in the performance_id
// from the inner query
QueryBuilder<Order, String> ordersQB = orderDao.queryBuilder();
ordersQB.where().isNull("user_id").and().in("performance_id", performancesQB);
ordersQB.orderByRaw("performances.date DESC");
pastOrdersQuery = ordersQB.prepare();
And the exception I'm getting whenever I try to execute this query is:
android.database.sqlite.SQLiteException: no such column: performances.date:,
while compiling: SELECT * FROM `orders` WHERE
(`user_id` IS NULL AND `performance_id` IN
(SELECT `performance_id` FROM `performances` WHERE `date` < ? ) )
ORDER BY performances.date DESC
The only solution I see here is writing a raw query myself using a JOIN instead of a nested select. May this be a good solution?
ORMLite now supports simple JOIN queries. Here the docs on the subject:
http://ormlite.com/docs/join-queries
So your query would now look something like:
QueryBuilder<Performance, String> performancesQB = performanceDao.queryBuilder();
SelectArg performanceSelectArg = new SelectArg();
performancesQB.where().lt("date", performanceSelectArg);
performancesQB.orderBy("date", false);
// query for Order objects, where the id matches
QueryBuilder<Order, String> ordersQB = orderDao.queryBuilder();
ordersQB.join(performancesQB).where().isNull("user_id");
pastOrdersQuery = ordersQB.prepare();

Categories