提问人:Chris Mazzola 提问时间:10/7/2008 最后编辑:JonasChris Mazzola 更新时间:1/16/2023 访问量:378682
PreparedStatement IN 子句替代方案?
PreparedStatement IN clause alternatives?
问:
将 SQL 子句与 实例一起使用的最佳解决方法是什么,由于 SQL 注入攻击安全问题,多个值不支持该实例: 一个占位符表示一个值,而不是值列表。IN
java.sql.PreparedStatement
?
请考虑以下 SQL 语句:
SELECT my_column FROM my_table where search_column IN (?)
使用本质上是一种非工作尝试,旨在解决首先使用的原因。preparedStatement.setString( 1, "'A', 'B', 'C'" );
?
有哪些解决方法?
答:
我从未尝试过,但是.setArray()会做你要找的吗?
更新:显然不是。 setArray 似乎仅适用于来自您从上一个查询中检索到的 ARRAY 列或具有 ARRAY 列的子查询的 java.sql.Array。
评论
我想您可以(使用基本的字符串操作)在 中生成查询字符串,使其数字与列表中的项目数相匹配。PreparedStatement
?
当然,如果你这样做,你离在查询中生成一个巨大的链子只有一步之遥,但是在查询字符串中没有正确数量的 ,我看不出你还能如何解决这个问题。OR
?
评论
尝试使用 INSTR 函数?
select my_column from my_table where instr(?, ','||search_column||',') > 0
然后
ps.setString(1, ",A,B,C,");
诚然,这有点肮脏,但它确实减少了 sql 注入的机会。无论如何都可以在 oracle 中工作。
评论
只是为了完整起见:只要值集不是太大,你也可以简单地字符串构造一个语句,比如
... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?
然后,您可以将其传递给 prepare(),然后在循环中使用 setXXX() 来设置所有值。这看起来很恶心,但许多“大型”商业系统通常会做这种事情,直到它们达到特定于数据库的限制,例如 Oracle 中的语句为 32 KB(我认为是)。
当然,您需要确保集合永远不会过大,或者在它太大的情况下进行错误捕获。
评论
遵循亚当的想法。使准备好的语句从my_table search_column in (#) 中选择 my_column 创建一个字符串 x 并用数字“?,?,?”填充它,具体取决于您的值列表 然后,只需在查询中更改新字符串 x 的 # 即可填充
一个令人不快但肯定可行的解决方法是使用嵌套查询。创建一个包含列的临时表 MYVALUES。将值列表插入到 MYVALUES 表中。然后执行
select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )
丑陋,但如果你的值列表非常大,这是一个可行的选择。
这种技术还有一个额外的优点,即优化器可能提供更好的查询计划(检查一个页面是否有多个值,表只扫描一次,而不是每个值一次,等等),如果数据库不缓存准备好的语句,则可以节省开销。您的“INSERTS”需要批量完成,并且可能需要调整 MYVALUES 表以具有最小的锁定或其他高开销保护。
评论
DROP TABLE myvalues IF EXISTS
CREATE TEMPORARY TABLE myvalues
没有简单的方法AFAIK。 如果目标是保持较高的语句缓存率(即不为每个参数计数创建语句),则可以执行以下操作:
创建一个包含几个(例如 10 个)参数的语句:
...其中 A 在 (?,?,?,?,?,?,?,?,?,?) ...
绑定所有实际参数
setString(1,“foo”); setString(2,“bar”);
将其余部分绑定为 NULL
setNull(3,类型.VARCHAR) ... setNull(10,类型.VARCHAR)
NULL 从不匹配任何内容,因此它由 SQL 计划生成器优化。
当您将 List 传递到 DAO 函数时,该逻辑很容易自动化:
while( i < param.size() ) {
ps.setString(i+1,param.get(i));
i++;
}
while( i < MAX_PARAMS ) {
ps.setNull(i+1,Types.VARCHAR);
i++;
}
评论
NULL
NULL
NOT IN
IN
select 'Matched' as did_it_match where 1 not in (5, null);
null
a IN (1,2,3,3,3,3,3)
a IN (1,2,3)
NOT IN
a NOT IN (1,2,3,null,null,null,null)
any_value != NULL
对各种可用选项的分析以及每个选项的优缺点可在 JavaRanch Journal 上 Jeanne Boyarsky 的 Batching Select Statements in JDBC 条目中找到。
建议的选项包括:
- 准备,为每个值执行它,并在客户端对结果执行 UNION。只需要一个准备好的语句。缓慢而痛苦。
SELECT my_column FROM my_table WHERE search_column = ?
- 准备并执行它。每个 size-of-IN 列表需要一个准备好的语句。快速而明显。
SELECT my_column FROM my_table WHERE search_column IN (?,?,?)
- 准备并执行它。[或者用分号代替那些分号。每个 size-of-IN 列表需要一个准备好的语句。愚蠢的慢,严格来说比 ,所以我不知道博主为什么会建议它。
SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
UNION ALL
WHERE search_column IN (?,?,?)
- 使用存储过程构造结果集。
- 准备 N 个不同大小的 IN-List 查询;比如说,有 2、10 和 50 个值。要搜索具有 6 个不同值的 IN 列表,请填充 size-10 查询,使其看起来像 。任何像样的服务器都会在运行查询之前优化掉重复的值。
SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
这些选项都不是理想的。
如果您使用的是 JDBC4 和支持 的服务器,最好的选择是按照 Boris 的 anwser 中的描述使用。x = ANY(y)
PreparedStatement.setArray
不过,似乎没有任何方法可以与 IN 列表一起使用。setArray
有时 SQL 语句在运行时加载(例如,从属性文件加载),但需要可变数量的参数。在这种情况下,请首先定义查询:
query=SELECT * FROM table t WHERE t.column IN (?)
接下来,加载查询。然后在运行之前确定参数的数量。知道参数计数后,运行:
sql = any( sql, count );
例如:
/**
* Converts a SQL statement containing exactly one IN clause to an IN clause
* using multiple comma-delimited parameters.
*
* @param sql The SQL statement string with one IN clause.
* @param params The number of parameters the SQL statement requires.
* @return The SQL statement with (?) replaced with multiple parameter
* placeholders.
*/
public static String any(String sql, final int params) {
// Create a comma-delimited list based on the number of parameters.
final StringBuilder sb = new StringBuilder(
String.join(", ", Collections.nCopies(possibleValue.size(), "?")));
// For more than 1 parameter, replace the single parameter with
// multiple parameter placeholders.
if (sb.length() > 1) {
sql = sql.replace("(?)", "(" + sb + ")");
}
// Return the modified comma-delimited list of parameters.
return sql;
}
对于某些不支持通过 JDBC 4 规范传递数组的数据库,此方法有助于将慢速子句条件转换为较快子句条件,然后可以通过调用该方法进行扩展。= ?
IN (?)
any
评论
在 PreparedStatement 中生成查询字符串,使其具有数字 ?'与列表中的项目数相匹配。下面是一个示例:
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;
}
评论
StringBuilder
generateQsForIn
StringBuilder
toString
StringBuilder
"x" + i+ "y" + j
ps.setObject(1,items)
paramteres
我的解决方法是:
create or replace type split_tbl as table of varchar(32767);
/
create or replace function split
(
p_list varchar2,
p_del varchar2 := ','
) return split_tbl pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;
l_value varchar2(32767);
begin
loop
l_idx := instr(l_list,p_del);
if l_idx > 0 then
pipe row(substr(l_list,1,l_idx-1));
l_list := substr(l_list,l_idx+length(p_del));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end split;
/
现在,您可以使用一个变量来获取表中的一些值:
select * from table(split('one,two,three'))
one
two
three
select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
value1 AAA
value2 BBB
因此,准备好的声明可以是:
"select * from TABLE where COL in (select * from table(split(?)))"
问候
哈维尔·伊巴涅斯
评论
Sormula 通过允许您提供 java.util.Collection 对象作为参数来支持 SQL IN 运算符。它创建一个带有 ?对于集合的每个元素。请参阅示例 4(示例中的 SQL 是一个注释,用于阐明 Sormula 创建但未使用的内容)。
PostgreSQL的解决方案:
final PreparedStatement statement = connection.prepareStatement(
"SELECT my_column FROM my_table where search_column = ANY (?)"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
try (ResultSet rs = statement.executeQuery()) {
while(rs.next()) {
// do some...
}
}
或
final PreparedStatement statement = connection.prepareStatement(
"SELECT my_column FROM my_table " +
"where search_column IN (SELECT * FROM unnest(?))"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
try (ResultSet rs = statement.executeQuery()) {
while(rs.next()) {
// do some...
}
}
评论
.createArrayOf()
Array
.createArrayOf
String arrayLiteral = "{A,\"B \", C,D}"
statement.setString(1,arrayLiteral)
... IN (SELECT UNNEST(?::VARCHAR[]))
... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
ANY
SELECT
而不是使用
SELECT my_column FROM my_table where search_column IN (?)
将 Sql 语句用作
select id, name from users where id in (?, ?, ?)
和
preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');
或使用存储过程,这将是最好的解决方案,因为 SQL 语句将被编译并存储在 DataBase 服务器中
我遇到了一些与准备好的声明相关的限制:
- 预准备语句仅缓存在同一会话 (Postgres) 中,因此它实际上仅适用于连接池
- @BalusC 提出的许多不同的预准备语句可能会导致缓存过满,并且以前缓存的语句将被删除
- 必须优化查询并使用索引。听起来很明显,但是例如,@Boris在顶级答案之一中提出的 ANY(ARRAY...) 语句不能使用索引,并且尽管缓存,查询速度会很慢
- 预准备语句也会缓存查询计划,并且语句中指定的任何参数的实际值都不可用。
在建议的解决方案中,我会选择不会降低查询性能且查询次数较少的解决方案。这将是 @Don 链接中的 #4(批处理几个查询)或为 @Vladimir Dyuzhev 提议的不需要的“?”标记指定 NULL 值
我们可以对 PreparedStatement 中的 IN 子句使用不同的替代方法。
- 使用单个查询 - 性能最慢且占用大量资源
- 使用 StoredProcedure - 最快但特定于数据库
- 为 PreparedStatement 创建动态查询 - 性能良好,但未获得缓存的好处,并且每次都会重新编译 PreparedStatement。
在 PreparedStatement 查询中使用 NULL - 最佳性能,当您知道 IN 子句参数的限制时,效果很好。如果没有限制,则可以批量执行查询。 示例代码片段是;
int i = 1; for(; i <=ids.length; i++){ ps.setInt(i, ids[i-1]); } //set null for remaining ones for(; i<=PARAM_SIZE;i++){ ps.setNull(i, java.sql.Types.INTEGER); }
您可以在此处查看有关这些替代方法的更多详细信息。
评论
在某些情况下,正则表达式可能会有所帮助。 这是我在 Oracle 上检查过的一个示例,它有效。
select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')
但它有许多缺点:
- 它应用的任何列都应该转换为 varchar/char,至少是隐式的。
- 需要小心特殊字符。
- 它会降低性能 - 在我的情况下,IN 版本使用索引和范围扫描,而 REGEXP 版本进行完全扫描。
在检查了不同论坛中的各种解决方案但没有找到一个好的解决方案后,我觉得我想出的以下技巧是最容易遵循和编码的:
示例:假设您有多个参数要在“IN”子句中传递。只需在“IN”子句中放置一个虚拟字符串,例如,“PARAM”表示将代替此虚拟字符串的参数列表。
select * from TABLE_A where ATTR IN (PARAM);
您可以在 Java 代码中将所有参数收集到单个 String 变量中。这可以按如下方式完成:
String param1 = "X";
String param2 = "Y";
String param1 = param1.append(",").append(param2);
在我们的例子中,您可以将所有参数附加到一个字符串变量“param1”中,用逗号分隔。
将所有参数收集到单个 String 中后,只需将查询中的虚拟文本(在本例中为“PARAM”)替换为参数 String(即 param1)即可。这是您需要做的:
String query = query.replaceFirst("PARAM",param1); where we have the value of query as
query = "select * from TABLE_A where ATTR IN (PARAM)";
您现在可以使用 executeQuery() 方法执行查询。只需确保查询中没有“PARAM”一词即可。您可以使用特殊字符和字母的组合来代替单词“PARAM”,以确保查询中不会出现此类单词。希望你得到解决方案。
注意:虽然这不是一个准备好的查询,但它完成了我希望我的代码完成的工作。
只是为了完整起见,因为我没有看到其他人提出建议:
在实现上述任何复杂建议之前,请考虑 SQL 注入在您的方案中是否确实是一个问题。
在许多情况下,提供给 IN (...) 的值是一个 ID 列表,这些 ID 是以一种可以确保无法注入的方式生成的......(例如,从some_table some_condition的上一个选择some_id的结果。
如果是这种情况,您可以只连接此值,而不使用服务或预准备语句,或者将它们用于此查询的其他参数。
query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
下面是 Java 中的完整解决方案,用于为您创建准备好的语句:
/*usage:
Util u = new Util(500); //500 items per bracket.
String sqlBefore = "select * from myTable where (";
List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5));
string sqlAfter = ") and foo = 'bar'";
PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId");
*/
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class Util {
private int numValuesInClause;
public Util(int numValuesInClause) {
super();
this.numValuesInClause = numValuesInClause;
}
public int getNumValuesInClause() {
return numValuesInClause;
}
public void setNumValuesInClause(int numValuesInClause) {
this.numValuesInClause = numValuesInClause;
}
/** Split a given list into a list of lists for the given size of numValuesInClause*/
public List<List<Integer>> splitList(
List<Integer> values) {
List<List<Integer>> newList = new ArrayList<List<Integer>>();
while (values.size() > numValuesInClause) {
List<Integer> sublist = values.subList(0,numValuesInClause);
List<Integer> values2 = values.subList(numValuesInClause, values.size());
values = values2;
newList.add( sublist);
}
newList.add(values);
return newList;
}
/**
* Generates a series of split out in clause statements.
* @param sqlBefore ""select * from dual where ("
* @param values [1,2,3,4,5,6,7,8,9,10]
* @param "sqlAfter ) and id = 5"
* @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)"
*/
public String genInClauseSql(String sqlBefore, List<Integer> values,
String sqlAfter, String identifier)
{
List<List<Integer>> newLists = splitList(values);
String stmt = sqlBefore;
/* now generate the in clause for each list */
int j = 0; /* keep track of list:newLists index */
for (List<Integer> list : newLists) {
stmt = stmt + identifier +" in (";
StringBuilder innerBuilder = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
innerBuilder.append("?,");
}
String inClause = innerBuilder.deleteCharAt(
innerBuilder.length() - 1).toString();
stmt = stmt + inClause;
stmt = stmt + ")";
if (++j < newLists.size()) {
stmt = stmt + " OR ";
}
}
stmt = stmt + sqlAfter;
return stmt;
}
/**
* Method to convert your SQL and a list of ID into a safe prepared
* statements
*
* @throws SQLException
*/
public PreparedStatement prepareStatements(String sqlBefore,
ArrayList<Integer> values, String sqlAfter, Connection c, String identifier)
throws SQLException {
/* First split our potentially big list into lots of lists */
String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier);
PreparedStatement ps = c.prepareStatement(stmt);
int i = 1;
for (int val : values)
{
ps.setInt(i++, val);
}
return ps;
}
}
Spring 允许将 java.util.Lists 传递给 NamedParameterJdbcTemplate ,这会根据参数的数量自动生成 (?, ?, ?, ..., ?)。
对于 Oracle,这篇博文讨论了 oracle.sql.ARRAY 的使用(Connection.createArrayOf 不适用于 Oracle)。为此,您必须修改 SQL 语句:
SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))
oracle 表函数将传递的数组转换为可在语句中使用的类似表的值。IN
in() 运算符的局限性是万恶之源。
它适用于微不足道的情况,您可以通过“自动生成准备好的语句”来扩展它,但它总是有其局限性。
- 如果要创建具有可变参数数的语句,则每次调用时都会产生 SQL 解析开销
- 在许多平台上,in() 运算符的参数数量是有限的
- 在所有平台上,总 SQL 文本大小都是有限的,因此不可能为 in 参数发送 2000 个占位符
- 无法向下发送 1000-10k 的绑定变量,因为 JDBC 驱动程序有其局限性
在某些情况下,in() 方法可能已经足够好了,但不能证明火箭:)
防火箭的解决方案是在单独的调用中传递任意数量的参数(例如,通过传递一堆参数),然后有一个视图(或任何其他方式)在 SQL 中表示它们并在 where 条件中使用。
蛮力变体在这里 http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
但是,如果您可以使用 PL/SQL,那么这种混乱会变得非常整洁。
function getCustomers(in_customerIdList clob) return sys_refcursor is
begin
aux_in_list.parse(in_customerIdList);
open res for
select *
from customer c,
in_list v
where c.customer_id=v.token;
return res;
end;
然后,您可以在参数中传递任意数量的逗号分隔的客户 ID,并且:
- 不会有解析延迟,因为 select 的 SQL 是稳定的
- 没有流水线函数的复杂性 - 它只是一个查询
- SQL使用简单的连接,而不是IN运算符,这非常快
- 毕竟,这是一个很好的经验法则,不要使用任何普通的选择或DML来攻击数据库,因为它是Oracle,它提供了比MySQL或类似的简单数据库引擎更多的光年。PL/SQL 允许您以有效的方式从应用程序域模型中隐藏存储模型。
这里的诀窍是:
- 我们需要一个接受长字符串的调用,并将其存储在数据库会话可以访问它的地方(例如,简单的包变量或dbms_session.set_context)
- 然后我们需要一个视图,可以将其解析为行
- 然后,您有一个包含要查询的 ID 的视图,因此您只需要对查询的表进行简单的联接即可。
视图如下所示:
create or replace view in_list
as
select
trim( substr (txt,
instr (txt, ',', 1, level ) + 1,
instr (txt, ',', 1, level+1)
- instr (txt, ',', 1, level) -1 ) ) as token
from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1
其中 aux_in_list.getpayload 是指原始输入字符串。
一种可能的方法是传递 pl/sql 数组(仅受 Oracle 支持),但您不能在纯 SQL 中使用它们,因此始终需要转换步骤。转换不能在 SQL 中完成,所以毕竟,传递一个包含字符串中所有参数的 clob 并在视图中进行转换是最有效的解决方案。
以下是我在自己的应用程序中解决它的方法。理想情况下,应使用 StringBuilder,而不是对字符串使用 +。
String inParenthesis = "(?";
for(int i = 1;i < myList.size();i++) {
inParenthesis += ", ?";
}
inParenthesis += ")";
try(PreparedStatement statement = SQLite.connection.prepareStatement(
String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
int x = 1;
statement.setLong(x++, race.startTime);
statement.setString(x++, race.name);
statement.setInt(x++, traderIdx);
for(String str : race.betFair.winners) {
statement.setString(x++, str);
}
int effected = statement.executeUpdate();
}
如果您决定稍后更改查询,则使用上面的 x 之类的变量而不是具体数字会很有帮助。
您可以使用此 javadoc 中提到的 setArray 方法:
PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"E1", "E2","E3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();
评论
我的解决方法 (JavaScript)
var s1 = " SELECT "
+ "FROM table t "
+ " where t.field in ";
var s3 = '(';
for(var i =0;i<searchTerms.length;i++)
{
if(i+1 == searchTerms.length)
{
s3 = s3+'?)';
}
else
{
s3 = s3+'?, ' ;
}
}
var query = s1+s3;
var pstmt = connection.prepareStatement(query);
for(var i =0;i<searchTerms.length;i++)
{
pstmt.setString(i+1, searchTerms[i]);
}
SearchTerms
是包含输入/键/字段等的数组
PreparedStatement 没有提供任何处理 SQL IN 子句的好方法。根据 http://www.javaranch.com/journal/200510/Journal200510.jsp#a2,“你不能替换那些本来要成为SQL语句一部分的东西。这是必要的,因为如果 SQL 本身可以更改,则驱动程序无法预编译语句。它还具有防止SQL注入攻击的良好副作用。我最终使用了以下方法:
String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)";
query = query.replace("$searchColumns", "'A', 'B', 'C'");
Statement stmt = connection.createStatement();
boolean hasResults = stmt.execute(query);
do {
if (hasResults)
return stmt.getResultSet();
hasResults = stmt.getMoreResults();
} while (hasResults || stmt.getUpdateCount() != -1);
SetArray 是最好的解决方案,但它不适用于许多较旧的驱动程序。以下解决方法可在 java8 中使用
String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)"
String markersString = inputArray.stream().map(e -> "?").collect(joining(","));
String sqlQuery = String.format(baseSQL, markersString);
//Now create Prepared Statement and use loop to Set entries
int index=1;
for (String input : inputArray) {
preparedStatement.setString(index++, input);
}
此解决方案优于其他丑陋的 while 循环解决方案,其中查询字符串是通过手动迭代构建的
评论
您可以使用以下命令生成占位符集合并连接它们:Collections.nCopies
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
*/
}
评论
我刚刚为此制定了一个特定于 PostgreSQL 的选项。它有点黑客攻击,并且有其自身的优点、缺点和局限性,但它似乎有效,并且不仅限于特定的开发语言、平台或 PG 驱动程序。
当然,诀窍是找到一种方法,将任意长度的值集合作为单个参数传递,并让 db 将其识别为多个值。我使用的解决方案是从集合中的值构造一个带分隔符的字符串,将该字符串作为单个参数传递,然后将 string_to_array() 与 PostgreSQL 的必要强制转换一起使用以正确使用它。
因此,如果要搜索“foo”、“blah”和“abc”,可以将它们连接成一个字符串:“foo,blah,abc”。下面是直接的 SQL:
select column from table
where search_column = any (string_to_array('foo,blah,abc', ',')::text[]);
显然,您将显式转换更改为您希望生成的值数组的任何内容 - int,text,uuid等。由于该函数采用单个字符串值(或者两个字符串值,如果您还想自定义分隔符),因此可以在准备好的语句中将其作为参数传递:
select column from table
where search_column = any (string_to_array($1, ',')::text[]);
这甚至足够灵活,可以支持 LIKE 比较之类的事情:
select column from table
where search_column like any (string_to_array('foo%,blah%,abc%', ',')::text[]);
同样,毫无疑问,这是一个黑客,但它是有效的,并且允许您仍然使用预编译的准备好的语句,这些语句采用*咳咳*离散参数,并附带安全性和(可能)性能优势。它是否可取且实际有效?当然,这取决于,因为在查询运行之前,您已经进行了字符串解析,并可能进行了强制转换。如果你希望发送三个、五个、几十个值,当然,这可能没问题。几千?是的,也许没有那么多。YMMV,限制和排除适用,不作任何明示或暗示的保证。
但它有效。
这对我有用(伪装代码):
public class SqlHelper
{
public static final ArrayList<String>platformList = new ArrayList<>(Arrays.asList("iOS","Android","Windows","Mac"));
public static final String testQuery = "select * from devices where platform_nm in (:PLATFORM_NAME)";
}
指定绑定:
public class Test extends NamedParameterJdbcDaoSupport
public List<SampleModelClass> runQuery()
{
//define rowMapper to insert in object of SampleClass
final Map<String,Object> map = new HashMap<>();
map.put("PLATFORM_LIST",DeviceDataSyncQueryConstants.platformList);
return getNamedParameterJdbcTemplate().query(SqlHelper.testQuery, map, rowMapper)
}
似乎还没有其他人建议使用现成的查询构建器,例如 jOOQ 或 QueryDSL,甚至是开箱即用管理动态 IN
列表的 Criteria Query,可能包括管理所有可能出现的边缘情况,例如:
- 遇到 Oracle 每个列表最多 1000 个元素(与绑定值的数量无关)
IN
- 遇到任何驱动程序的最大绑定值数,我已在此答案中记录了这一点
- 遇到游标缓存争用问题,因为太多不同的 SQL 字符串被“硬解析”并且执行计划无法再缓存(jOOQ 并且最近 Hibernate 也通过提供
IN
列表填充来解决这个问题)
(免责声明:我在jOOQ背后的公司工作)
好吧,所以我不记得我以前是怎么(或在哪里)这样做的,所以我来到 stack overflow 以快速找到答案。我很惊讶我不能。
所以,很久以前我是如何解决 IN 问题的,就是用这样的语句:
其中 myColumn in ( select regexp_substr(:myList,'[^,]+', 1, level) from dual connect by regexp_substr(:myList, '[^,]+', 1, level) is not null)
将 myList 参数设置为逗号分隔的字符串:A,B,C,D...
注意:您必须设置两次参数!
这不是理想的做法,但它很简单,而且大多数时候对我来说效果很好。
where ? like concat( "%|", TABLE_ID , "|%" )
然后你通过?ID 如下:|1|,|2|,|3|,...|
你可以只使用 PreparedStatement 的 setArray() 方法
PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)");
statement.setArray(1, Arrays.asList(1,2,3,4,5));
ResultSet rs = statement.executeQuery();
评论