当前位置:  开发笔记 > 编程语言 > 正文

PreparedStatement IN子句替代?

如何解决《PreparedStatementIN子句替代?》经验,为你挑选了8个好方法。

将SQL IN子句与实例一起使用的最佳解决方法java.sql.PreparedStatement是什么,由于SQL注入攻击安全问题,多个值不支持这种解决方法:一个?占位符代表一个值,而不是值列表.

请考虑以下SQL语句:

SELECT my_column FROM my_table where search_column IN (?)

使用preparedStatement.setString( 1, "'A', 'B', 'C'" );本质上是一种非工作尝试,?首先使用的原因是解决方法.

有哪些变通方法?



1> Dónal..:

此处提供了对各种可用选项的分析,以及各自的优缺点.

建议的选项是:

准备SELECT my_column FROM my_table WHERE search_column = ?,为每个值执行它,并在结果客户端UNION.只需要一份准备好的声明.缓慢而痛苦.

准备SELECT my_column FROM my_table WHERE search_column IN (?,?,?)并执行它.每个IN-list大小需要一个准备好的语句.快速而明显.

准备SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...并执行它.[或者用来UNION ALL代替那些分号. - 每个大小的IN列表需要一个准备好的语句.愚蠢的慢,严格的比WHERE search_column IN (?,?,?),所以我不知道为什么博客甚至建议它.

使用存储过程构造结果集.

准备N个不同大小的IN列表查询; 比方说,有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).任何体面的服务器都会在运行查询之前优化重复值.

但是,这些选项都不是超级棒.

在这些地方已经回答了重复的问题,同样有理智的选择,但仍然没有一个非常棒:

带有IN子句中参数列表的PreparedStatement

如何在准备好的语句中设置参数列表?

正确的答案,如果您使用的是JDBC4和支持的服务器,请按照此处的说明x = ANY(y)使用PreparedStatement.setArray:

PreparedStatement IN子句替代?

但是,似乎没有任何方法可以setArray使用IN列表.


有时,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(
            new String(new char[params]).replace("\0", "?,")
    );

    // Remove trailing comma.
    sb.setLength(Math.max(sb.length() - 1, 0));

    // 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方法进行扩展.



2> Boris..:

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));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

要么

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));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}


如果`.createArrayOf`不起作用,你可以自己手动创建数组文字,如`String arrayLiteral ="{A,\"B \",C,D}"`_(注意"B"有一个空间,而C不)_然后`statement.setString(1,arrayLiteral)`其中准备好的语句是`... IN(SELECT UNNEST(?:: VARCHAR []))`或`... IN( SELECT UNNEST(CAST(?AS VARCHAR [])))`.(PS:我不认为`ANY`适用于'SELECT`.)

3> Vladimir Dyu..:

没有简单的方法AFAIK.如果目标是保持语句高速缓存比率高(即不为每个参数计数创建语句),则可以执行以下操作:

    使用一些(例如10个)参数创建一个语句:

    ......在哪里(?,?,?,?,?,?,?,?,?,?)......

    绑定所有实际参数

    的SetString(1, "富"); 的SetString(2, "巴");

    将其余部分绑定为NULL

    setNull(3,Types.VARCHAR)... setNull(10,Types.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++;
}


@CraigMcQueen不,不会.根据ANSI标准,Null甚至不匹配null.
但要小心这一点.`NOT IN`和`IN`不会以相同的方式处理空值.运行它,看看会发生什么:`select'匹配'作为did_it_match,其中1不在(5,null);`然后删除`null`并观察魔法.

4> James Schek..:

一个令人不快的解决方法,但肯定是可行的是使用嵌套查询.创建一个临时表MYVALUES,其中包含一列.将值列表插入MYVALUES表.然后执行

select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )

丑陋,但如果您的值列表非常大,那么这是一个可行的选择.

这种技术的另一个优点是,如果数据库不缓存预准备语句,优化器可能会提供更好的查询计划(检查页面是否存在多个值,表格只扫描一次,而不是每个值一次,等等)可以节省开销.您的"插入"需要批量完成,并且可能需要调整MYVALUES表以获得最小的锁定或其他高开销保护.


查询优化器可以通过从加载的页面检索所有可能的匹配来减少I/O负载.表扫描或索引扫描可以执行一次,而不是每个值执行一次.使用批处理操作可以减少插入值的开销,并且可能少于几个查询.

5> Gee Bee..:

in()运算符的局限性是万恶之源.

它适用于琐碎的情况,您可以通过"自动生成准备好的语句"来扩展它,但它始终有其限制.

如果您正在创建具有可变数量参数的语句,那么将在每次调用时产生sql解析开销

在许多平台上,in()运算符的参数数量是有限的

在所有平台上,总的SQL文本大小是有限的,因此无法向params发送2000个占位符

发送1000-10k的绑定变量是不可能的,因为JDBC驱动程序有其局限性

对于某些情况,in()方法可以很好,但不能防火箭:)

防火解决方案是在单独的调用中传递任意数量的参数(例如,通过传递一些参数),然后使用视图(或任何其他方式)在SQL中表示它们并在您的位置使用标准.

这里有一个蛮力变体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 for SQL是稳定的

没有流水线功能复杂性 - 它只是一个查询

SQL使用简单的连接,而不是IN运算符,这非常快

毕竟,这是一个很好的经验规则没有击中任何普通的数据库中选择或DML,因为它是Oracle,它提供了比MySQL或类似的简单的数据库引擎更光年.PL/SQL允许您以有效的方式从应用程序域模型中隐藏存储模型.

这里的诀窍是:

我们需要一个接受长字符串的调用,并存储在db会话可以访问它的地方(例如,简单的包变量或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并在视图中转换它是最有效的解决方案.



6> 小智..:

这是我在自己的应用程序中解决它的方式.理想情况下,您应该使用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等变量而不是具体数字会有很大帮助.



7> Paul Tomblin..:

我从来没有尝试过,但是.setArray()会做你想要的吗?

更新:显然不是.setArray似乎只适用于来自您从先前查询检索到的ARRAY列的java.sql.Array,或者带有ARRAY列的子查询.


不适用于所有数据库,但这是"正确"的方法.

8> 小智..:

我的解决方法是:

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(?)))"

问候,

哈维尔·伊瓦涅斯

推荐阅读
U友50081205_653
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有