Question

I have some code that produces a set of primary key values that I want to delete from a database table.

long[] keysToDelete = { 0, 1, 2, 3 };

and I'd like to use a PreparedStatement to execute the equivalent of

DELETE FROM MyTable WHERE myPrimaryKey IN (0, 1, 2, 3);

Any idea how?

Was it helpful?

Solution

Two steps:

  1. Build up the PreparedStatement SQL String with the appropriate # of parameters.
  2. Loop over the array of values and bind each one to its parameter.

Unfortunately, there's no good way to bind an array all at once.

OTHER TIPS

I've written a class to dynamically generate such a multi-parameter query. It currently has some limitations (for quickness of writing) and has not been thoroughly tested, but may be a good way to get you started. Limitations:

  • Only handles one multi-argument parameter (??)
  • Falsely recognizes question marks in quotes as parameters
  • API is not pretty but the alternative was writing a full-on PreparedStatement decorator with lots of state management and that was more work than I was willing to put into it.

Source:

/**
 * A PreparedStatement decorator that can bind a set of arguments
 *
 * A specialized ?? placeholder in a string can be bound to a set of
 * values instead of just single values. Currently, only one such
 * specialized placeholder is supported, and you must bind it before
 * obtaining the prepared statement.
 *
 * If you want to bind additional values to the PreparedStatement after
 * producing it, you must run the parameter index through the param()
 * method.
 *
 * Example use:
 *
 * 
 *     MultiValueBinder binder = new MultiValueBinder(
 *          "UPDATE table SET value = ? WHERE id IN (??)", conn);
 *     binder.setInts(myIds);
 *
 *     PreparedStatement stmt = binder.statement();
 *     stmt.setString(binder.param(1), "myValue");
 *
 *     ResultSet rs = stmt.executeQuery();
 *
 * Note: this class is not robust against using question marks in literal
 * strings. Don't use them :).
 */
public class MultiValueBinder {
    private Connection connection;
    private PreparedStatement statement;
    private String sql;

    private int argumentsBefore = 0;
    private int setSize         = 0;

    public MultiValueBinder(String sql, Connection connection) {
        this.sql        = sql;
        this.connection = connection;
    }

    /**
     * Bind a collection of integers to the multi-valued argument
     */
    public void setInts(Collection<Integer> ints) throws SQLException {
        explodeQuery(ints.size());
        buildStatement();
        try {

            int i = 0;
            for (Integer integer: ints)
                statement.setInt(1 + argumentsBefore + i++, integer);

        } catch (Exception ex) {
            cleanStatement();
            throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
        }
    }

    /**
     * Bind a collection of strings to the multi-valued argument
     */
    public void setStrings(Collection<String> strings) throws SQLException {
        explodeQuery(strings.size());
        buildStatement();
        try {

            int i = 0;
            for (String str: strings)
                statement.setString(1 + argumentsBefore + i++, str);

        } catch (Exception ex) {
            cleanStatement();
            throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
        }
    }

    /**
     * Explode the multi-value parameter into a sequence of comma-separated
     * question marks.
     */
    private void explodeQuery(int size) throws SQLException {
        int mix = sql.indexOf("??");
        if (mix == -1) throw new SQLException("Query does not contain a multi-valued argument.");
        if (size == 0) throw new SQLException("Can't bind an empty collection; generated SQL won't parse.");

        // Count the number of arguments before the multi-marker
        argumentsBefore = 0;
        for (int i = 0; i < mix; i++) {
            if (sql.charAt(i) == '?') argumentsBefore++;
        }
        setSize = size;

        // Generate the exploded SQL query
        StringBuilder sb = new StringBuilder(sql.substring(0, mix)); // Start
        for (int i = 0; i < setSize; i++) {                          // ?, ?, ...
            if (i > 0) sb.append(", ");
            sb.append('?');
        }
        sb.append(sql.substring(mix + 2));                           // Remainder
        sql = sb.toString();
    }

    /**
     * Create the statement if it hasn't been created yet
     */
    private void buildStatement() throws SQLException {
        if (statement != null) return;
        if (sql.contains("??"))
            throw new SQLException("Multi-valued argument not bound yet.");
        statement = connection.prepareStatement(sql);
    }

    private void cleanStatement() {
        if (statement != null) {
            try {
                statement.close();
            } catch (Exception ex) {
                /* Ignore */
            }
            statement = null;
        }
    }

    public PreparedStatement statement() throws SQLException {
        buildStatement();
        return statement;
    }

    /**
     * Transform the 1-based-index of the given argument before query expansion
     * into the index after expansion.
     *
     * The ?? placeholder takes up one index slot.
     */
    public int param(int ix) {
        if (ix <= argumentsBefore) return ix;
        if (ix == argumentsBefore + 1)
            throw new RuntimeException(ix + " is the index of the multi-valued parameter.");
        return argumentsBefore + 1 + setSize;
    }
}

Not totally sure but this might help:

PreparedStatement pstmt = Connection.prepareStatement("DELETE FROM MyTable WHERE myPrimaryKey IN (?)");
pstmt.setArray(1, idArray);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top