PreparedStatement IN clause alternatives?

前端 未结 30 3895
情歌与酒
情歌与酒 2020-11-21 05:19

What are the best workarounds for using a SQL IN clause with instances of java.sql.PreparedStatement, which is not supported for multiple values du

相关标签:
30条回答
  • 2020-11-21 05:51

    An analysis of the various options available, and the pros and cons of each is available here.

    The suggested options are:

    • Prepare SELECT my_column FROM my_table WHERE search_column = ?, execute it for each value and UNION the results client-side. Requires only one prepared statement. Slow and painful.
    • Prepare SELECT my_column FROM my_table WHERE search_column IN (?,?,?) and execute it. Requires one prepared statement per size-of-IN-list. Fast and obvious.
    • Prepare SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... and execute it. [Or use UNION ALL in place of those semicolons. --ed] Requires one prepared statement per size-of-IN-list. Stupidly slow, strictly worse than WHERE search_column IN (?,?,?), so I don't know why the blogger even suggested it.
    • Use a stored procedure to construct the result set.
    • Prepare N different size-of-IN-list queries; say, with 2, 10, and 50 values. To search for an IN-list with 6 different values, populate the size-10 query so that it looks like SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6). Any decent server will optimize out the duplicate values before running the query.

    None of these options are super great, though.

    Duplicate questions have been answered in these places with equally sane alternatives, still none of them super great:

    • PreparedStatement with list of parameters in a IN clause
    • How to set list of parameters on prepared statement?

    The Right Answer, if you are using JDBC4 and a server that supports x = ANY(y), is to use PreparedStatement.setArray as described here:

    • PreparedStatement IN clause alternatives?

    There doesn't seem to be any way to make setArray work with IN-lists, though.


    Sometimes SQL statements are loaded at runtime (e.g., from a properties file) but require a variable number of parameters. In such cases, first define the query:

    query=SELECT * FROM table t WHERE t.column IN (?)
    

    Next, load the query. Then determine the number of parameters prior to running it. Once the parameter count is known, run:

    sql = any( sql, count );
    

    For example:

    /**
     * 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;
    }
    

    For certain databases where passing an array via the JDBC 4 specification is unsupported, this method can facilitate transforming the slow = ? into the faster IN (?) clause condition, which can then be expanded by calling the any method.

    0 讨论(0)
  • 2020-11-21 05:51

    There are different alternative approaches that we can use for IN clause in PreparedStatement.

    1. Using Single Queries - slowest performance and resource intensive
    2. Using StoredProcedure - Fastest but database specific
    3. Creating dynamic query for PreparedStatement - Good Performance but doesn't get benefit of caching and PreparedStatement is recompiled every time.
    4. Use NULL in PreparedStatement queries - Optimal performance, works great when you know the limit of IN clause arguments. If there is no limit, then you can execute queries in batch. Sample code snippet is;

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

    You can check more details about these alternative approaches here.

    0 讨论(0)
  • 2020-11-21 05:54

    Solution for 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...
        }
    }
    

    or

    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...
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:54

    I've never tried it, but would .setArray() do what you're looking for?

    Update: Evidently not. setArray only seems to work with a java.sql.Array that comes from an ARRAY column that you've retrieved from a previous query, or a subquery with an ARRAY column.

    0 讨论(0)
  • 2020-11-21 05:54

    You could use setArray method as mentioned in this javadoc:

    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();
    
    0 讨论(0)
  • 2020-11-21 05:54

    Just for completeness: So long as the set of values is not too large, you could also simply string-construct a statement like

    ... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?
    

    which you could then pass to prepare(), and then use setXXX() in a loop to set all the values. This looks yucky, but many "big" commercial systems routinely do this kind of thing until they hit DB-specific limits, such as 32 KB (I think it is) for statements in Oracle.

    Of course you need to ensure that the set will never be unreasonably large, or do error trapping in the event that it is.

    0 讨论(0)
提交回复
热议问题