【问题标题】:Efficient way to select data with a single condition使用单一条件选择数据的有效方法
【发布时间】:2015-09-17 17:55:17
【问题描述】:

有没有一种有效的方法来获取列表(最好是数组,ResultSet 可以)到SELECT 很多行。

例如:

Connection con = DriverManager.getConnection(host,username,password);
String sql = "SELECT * FROM table_name WHERE food = ? AND expiration > ?";
PreparedStatement stmt = con.prepareStatement(sql);

使用上面的代码,我想从给定数组中获取所有未过期的食物。

String[] foodList  = {"banana","apple","orange",...}

到期日期是固定日期(比如说 3 天前)。但是,我的方法是 String 和 PreparedStatement 在一个 for 循环中,该循环循环数组中的食物数量以单独检查过期日期。在我单独执行每个之后,这会创建很多 ResultSets。

【问题讨论】:

  • 您需要单独的每个结果吗?如果没有,为什么不使用 select blah blah where food IN (" + list + ") and expiration > ?
  • 我试图抢走所有没有过期的食物@eckes
  • @alp 我实际上并不单独需要它们,我会考虑使用in 编辑:sean Bright 向我展示了在这种情况下我将如何使用in
  • @Brian 请检查文档。可能会有绩效回报。我不记得 IN 是否使用索引。如果您使用该路线并使用 IN,请务必确认。
  • @alp 谢谢!我读到 IN 有点快

标签: java sql arrays database select


【解决方案1】:

可能不是最优雅的解决方案,并且您不会从准备好的语句中获得任何性能优势(但您会获得参数绑定):

StringBuilder sql = new StringBuilder("SELECT * FROM table_name WHERE expiration > ? AND food IN (");

for (int i = 0; i < foodList.length; i++) {
    if (i > 0) {
        sql.append(',');
    }
    sql.append('?');
}

sql.append(")");

Connection con = DriverManager.getConnection(host, username, password);
PreparedStatement stmt = con.prepareStatement(sql.toString());

stmt.setDate(1, expirationDate);
for (int i = 0; i < foodList.length; i++) {
    stmt.setString(i + 2, foodList[i]);
}

ResultSet rs = stmt.executeQuery();

/* ... Do Stuff ... */

【讨论】:

    【解决方案2】:

    大多数 SQL 数据库都支持 IN (list) 表达式。这大致相当于提供一个 or 表达式:

    SELECT id FROM table WHERE food IN ('Apple', 'Banana')  AND exp < ?
    

    类似于

    SELECT id FROM table WHERE (food = 'Apple' or food = 'Banana') AND exp < ?
    

    在这两种情况下,一些 RDBMS 都可以对其进行优化。

    但是首先,您可以在 IN 中指定的列表项的数量或您可以在语句中使用的字符数存在限制。因此,如果您的列表可以变长,您需要准备好运行多个语句。

    其次,您不能*将数组设置为 PreparedStatement 的参数并期望它与 IN 一起使用。

    不幸的是,在普通的 JDBC 中,您所能做的就是连接一个字符串。这是不受欢迎的,但没有好的选择(除非你想做一些事情,比如将食物列表作为一个列表并使用“instring”表达式)。

    确保添加与您期望的参数一样多的?(但不要太多),然后在IN中设置它们:

    String[] foods = ...;
    int remain = foods.length;
    int start = 0;
    while(remain > 0)
    {  if (remain >= 100)
         executeBatch(foods, start, 100); start+=100; remain-=100;
       else if (remain >= 30)
         executeBatch(foods, start, 30); start+=30; remain-=30;
       else {
         executeBatch(foods, start, 1); start+=1; remain-=1;
       }
    }
    
    
    void executeBatch(String[] f, int off, int len)
    {
        StringBuilder sqlBuf = StringBuilder("... IN(");
        for(int i=0;i<len;i++) {
            sqlBuf.append((i!=0)?",?":"?");
        }
        String sql = sqlBuf.append(") AND exp < ?").toString();
        PreparedStatement ps = c.prepareStatement(sql);
        for(int i=0;i<foods.length;i++)
            ps.setString(i+1, foods[i+off]);
        ps.setTimestamp(foods.length+1, now);
        ....
    }
    

    这样可以避免生成很多不同的 SQL 语句来编译。 (只有 100,30 或 1 个?))。您可以对 OR 情况使用相同的逻辑。

    * 不要与 ARRAY 数据库类型混淆。

    【讨论】:

    • 绝妙的方法...但我有一个疑问..为什么您将上述逻辑分解为 100,30,1 而不是直接从 0 变为 food.lenght()... 可以请你解释一下?..
    • 如果您有大量不同长度的批次,那么它们中的每一个都将是一个新的 SQL 语句,从而导致 Oracle 端的硬解析。如果您只有几个选定的参数编号(在我的示例中为 3 个),它们将被缓存和软解析。所以你降低了 SQL 区域的污染。很少这样做或方差很小(或恒定)都没有关系。
    猜你喜欢
    • 2019-02-02
    • 1970-01-01
    • 1970-01-01
    • 2018-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-23
    相关资源
    最近更新 更多