使用多列的参数化IN子句

时间:2016-02-05 17:45:48

标签: java postgresql spring-jdbc

我有这样的查询,我试图通过比较元组(如SQL multiple columns in IN clause)来过滤结果集:

select *
from mytable
where (key, value) in (values
 ('key1', 'value1'),
 ('key2', 'value2'),
 ...
);

这是有效的语法,可以在我的Postgres 9.3数据库中正常使用。

我想通过Spring JDBC调用此查询,其中in值对来自List<Map<String,String>>

做这样的事情会很好:

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);

当我尝试这个时,我得到:

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]

我看过它提到的hstore扩展名。它似乎与我的问题无关。

有没有办法在不动态构建SQL和参数列表的情况下完成此任务?

4 个答案:

答案 0 :(得分:8)

您所要做的就是传递一个数组列表,其中每个数组都包含一个键和值,如下所示:

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);

Spring文档中提到了这一点:http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/jdbc.html#jdbc-in-clause

答案 1 :(得分:1)

如果您无法使解决方案正常工作,您还可以将关键字和值连接起来。 对于这种更基本的语法,JDBC可能没有什么问题:

select *
from mytable
where (key||value) in (
 ('key1value1'),
 ('key2value2'),
 ...
);

为了实现这一点,您需要首先将Java Map转换为List,并且键和值也会连接在一起。

答案 2 :(得分:0)

不幸的是,没有任何简单的方法可以将嵌套的集合绑定变量绑定到PostgreSQL。您可以生成以下SQL字符串

SELECT *
FROM mytable
WHERE (key, value) IN (
  (?, ?),
  (?, ?),
  ...
);

保持SQL字符串和变量绑定同步是一项工作。 但是,您可以将地图编码为JSON:

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)
)

在这种情况下,您只需要一个VARCHAR绑定变量

答案 3 :(得分:-1)

查询可能不是问题,可能是您没有创建/安装任何hstore。

我建议您按照以下步骤调试问题:

  1. 尝试一个非常简单的查询,不带任何参数。
  2. 如果您遇到同样的问题,请查看如何创建扩展程序:http://www.postgresql.org/docs/9.1/static/sql-createextension.html
  3. 否则,如果查询执行正确,请尝试使用简单参数(使用=?
  4. 最后,尝试命名查询。类似的东西:
  5. 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;
    

    同时查看此讨论,我发现它很有用:How to execute IN() SQL queries with Spring's JDBCTemplate effectivly?