web-dev-qa-db-ja.com

IN句にパラメータのリストを含むPreparedStatement

クエリの実行中にJDBCのpreparedStatementのin句の値を設定する方法。

例:

connection.prepareStatement("Select * from test where field in (?)");

この節が複数の値を保持できる場合、どうすればよいですか。パラメータのリストを事前に知っている場合もあれば、事前に知らない場合もあります。この場合の対処方法は?

80
Harish

私がしていることは、「?」を追加することです可能な値ごとに。

例えば:

List possibleValues = ... 
StringBuilder builder = new StringBuilder();

for( int i = 0 ; i < possibleValue.size(); i++ ) {
    builder.append("?,");
}

String stmt = "select * from test where field in (" 
               + builder.deleteCharAt( builder.length() -1 ).toString() + ")";
PreparedStatement pstmt = ... 

そして、喜んでパラメータを設定します

int index = 1;
for( Object o : possibleValue ) {
   pstmt.setObject(  index++, o ); // or whatever it applies 
}
86
OscarRyz

以下のjavadocに記載されているsetArrayメソッドを使用できます。

http://docs.Oracle.com/javase/6/docs/api/Java/sql/PreparedStatement.html#setArray(int、Java.sql.Array)

コード:

PreparedStatement statement = connection.prepareStatement("Select * from test where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"A1", "B2","C3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();
44
madx

次のリンクを確認してください。

http://www.javaranch.com/journal/200510/Journal200510.jsp#a2

PreparedStatement句でinを作成するさまざまな方法の長所と短所について説明します。

編集:

明らかなアプローチは、「?」を動的に生成することです実行時に分けますが、使用する方法によっては非効率になる可能性があるため、このアプローチだけを提案したくありません(使用するたびにPreparedStatementを「コンパイル」する必要があるため)

14
ryanprayogo

クエリの?を任意の数の値に置き換えることはできません。各?は、単一の値のみのプレースホルダーです。任意の数の値をサポートするには、?, ?, ?, ... , ?を含む文字列を動的に構築する必要があります。疑問符の数は、in句に必要な値の数と同じです。

7
Asaph

Jdbc4が必要な場合は、setArrayを使用できます!

私の場合、postgresのUUIDデータ型にはまだ弱点があるように見えましたが、通常の型では機能します。

ps.setArray(1, connection.createArrayOf("$VALUETYPE",myValuesAsArray));

もちろん、$ VALUETYPEとmyValuesAsArrayを正しい値に置き換えます。

マークのコメントに続くコメント:

データベースとドライバーはこれをサポートする必要があります! Postgres 9.4を試しましたが、これは以前に導入されたと思います。 jdbc 4ドライバーが必要です。そうでない場合、setArrayは使用できません。スプリングブートに同梱されているpostgresql 9.4-1201-jdbc41ドライバーを使用しました

3

IN句を使用した動的クエリでPreparedStatmentを使用したくない場合は、少なくとも5変数またはそのような小さい値を常に使用しますが、それでも悪い考えだと思います(ひどくはありませんが、悪い)。要素の数が多いほど、悪化します(ひどい)。

IN句で数百または数千の可能性を想像してください。

  1. これは逆効果であり、新しいリクエストが発生するたびにキャッシュするため、パフォーマンスとメモリが失われます。また、PreparedStatementはSQLインジェクションだけではなく、パフォーマンスに関するものです。この場合、Statementの方が優れています。

  2. あなたのプールにはPreparedStatmentの制限があります(デフォルトでは-1ですが、制限する必要があります)。この制限に達するでしょう!制限がないか、非常に大きな制限がある場合は、メモリリークのリスクがあり、極端な場合はOutofMemoryエラーが発生します。したがって、3人のユーザーが使用する小規模な個人用プロジェクトの場合は劇的ではありませんが、大企業にいて、アプリが1,000人のユーザーと100万のリクエストで使用されている場合は、その必要はありません。

いくつかの読書。 IBM:プリペアドステートメントキャッシュを使用する場合のメモリ使用率に関する考慮事項

2
amdev
public static ResultSet getResult(Connection connection, List values) {
    try {
        String queryString = "Select * from table_name where column_name in";

        StringBuilder parameterBuilder = new StringBuilder();
        parameterBuilder.append(" (");
        for (int i = 0; i < values.size(); i++) {
            parameterBuilder.append("?");
            if (values.size() > i + 1) {
                parameterBuilder.append(",");
            }
        }
        parameterBuilder.append(")");

        PreparedStatement statement = connection.prepareStatement(queryString + parameterBuilder);
        for (int i = 1; i < values.size() + 1; i++) {
            statement.setInt(i, (int) values.get(i - 1));
        }

        return statement.executeQuery();
    } catch (Exception d) {
        return null;
    }
}
2

できることは、IN句に入れる必要のある値の数がわかったらすぐに、単純なforループによって選択文字列( 'IN(?)'部分)を動的に構築することです。その後、PreparedStatementをインスタンス化できます。

2
rfk

現在、MySQLでは1つのメソッド呼び出しで複数の値を設定することはできません。だから、あなたはあなた自身の管理下にある必要があります。通常、事前定義された数のパラメーターに対して1つの準備済みステートメントを作成し、必要な数のバッチを追加します。

    int paramSizeInClause = 10; // required to be greater than 0!
    String color = "FF0000"; // red
    String name = "Nathan"; 
    Date now = new Date();
    String[] ids = "15,21,45,48,77,145,158,321,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,358,1284,1587".split(",");

    // Build sql query 
    StringBuilder sql = new StringBuilder();
    sql.append("UPDATE book SET color=? update_by=?, update_date=? WHERE book_id in (");
    // number of max params in IN clause can be modified 
    // to get most efficient combination of number of batches
    // and number of parameters in each batch
    for (int n = 0; n < paramSizeInClause; n++) {
        sql.append("?,");
    }
    if (sql.length() > 0) {
        sql.deleteCharAt(sql.lastIndexOf(","));
    }
    sql.append(")");

    PreparedStatement pstm = null;
    try {
        pstm = connection.prepareStatement(sql.toString());
        int totalIdsToProcess = ids.length;
        int batchLoops = totalIdsToProcess / paramSizeInClause + (totalIdsToProcess % paramSizeInClause > 0 ? 1 : 0);
        for (int l = 0; l < batchLoops; l++) {
            int i = 1;
            pstm.setString(i++, color);
            pstm.setString(i++, name);
            pstm.setTimestamp(i++, new Timestamp(now.getTime()));
            for (int count = 0; count < paramSizeInClause; count++) {
                int param = (l * paramSizeInClause + count);
                if (param < totalIdsToProcess) {
                    pstm.setString(i++, ids[param]);
                } else {
                    pstm.setNull(i++, Types.VARCHAR);
                }
            }
            pstm.addBatch();
        }
    } catch (SQLException e) {
    } finally {
        //close statement(s)
    }

パラメーターが残っていないときにNULLを設定したくない場合は、コードを変更して2つのクエリと2つの準備されたステートメントを作成できます。最初のものは同じですが、残りの2番目のステートメント(モジュラス)です。この特定の例では、10個のパラメータに対して1つのクエリ、8個のパラメータに対して1つのクエリになります。最初のクエリに3つのバッチ(最初の30パラメーター)を追加し、2番目のクエリに1つのバッチ(8パラメーター)を追加する必要があります。

1
A Kunin
public class Test1 {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("helow");
String where="where task in ";
        where+="(";
    //  where+="'task1'";
        int num[]={1,2,3,4};
        for (int i=0;i<num.length+1;i++) {
            if(i==1){
                where +="'"+i+"'";
            }
            if(i>1 && i<num.length)
                where+=", '"+i+"'";
            if(i==num.length){
                System.out.println("This is last number"+i);
            where+=", '"+i+"')";
            }
        }
        System.out.println(where);  
    }
}
1
user3870246

以下を使用できます。

for( int i = 0 ; i < listField.size(); i++ ) {
    i < listField.size() - 1 ? request.append("?,") : request.append("?");
}

それから:

int i = 1;
for (String field : listField) {
    statement.setString(i++, field);
}

例:

List<String> listField = new ArrayList<String>();
listField.add("test1");
listField.add("test2");
listField.add("test3");

StringBuilder request = new StringBuilder("SELECT * FROM TABLE WHERE FIELD IN (");

for( int i = 0 ; i < listField.size(); i++ ) {
    request = i < (listField.size() - 1) ? request.append("?,") : request.append("?");
}


DNAPreparedStatement statement = DNAPreparedStatement.newInstance(connection, request.toString);

int i = 1;
for (String field : listField) {
    statement.setString(i++, field);
}

ResultSet rs = statement.executeQuery();
1
Simon Barbier

このコードで試してください

 String ids[] = {"182","160","183"};
            StringBuilder builder = new StringBuilder();

            for( int i = 0 ; i < ids.length; i++ ) {
                builder.append("?,");
            }

            String sql = "delete from emp where id in ("+builder.deleteCharAt( builder.length() -1 ).toString()+")";

            PreparedStatement pstmt = connection.prepareStatement(sql);

            for (int i = 1; i <= ids.length; i++) {
                pstmt.setInt(i, Integer.parseInt(ids[i-1]));
            }
            int count = pstmt.executeUpdate();
0
Narendra

多くのDBには一時テーブルの概念があります。一時テーブルがない場合でも、一意の名前を持つテーブルをいつでも生成し、作業が終了したら削除できます。テーブルの作成と削除のオーバーヘッドは大きいですが、これは非常に大規模な操作や、データベースをローカルファイルまたはメモリ(SQLite)として使用している場合には合理的です。

(Java/SqlLiteを使用して)途中にあるものの例:

String tmptable = "tmp" + UUID.randomUUID();

sql = "create table " + tmptable + "(pagelist text not null)";
cnn.createStatement().execute(sql);

cnn.setAutoCommit(false);
stmt = cnn.prepareStatement("insert into "+tmptable+" values(?);");
for(Object o : rmList){
    Path path = (Path)o;
    stmt.setString(1, path.toString());
    stmt.execute();
}
cnn.commit();
cnn.setAutoCommit(true);

stmt = cnn.prepareStatement(sql);
stmt.execute("delete from filelist where path + page in (select * from "+tmptable+");");
stmt.execute("drop table "+tmptable+");");

テーブルで使用されるフィールドは動的に作成されることに注意してください。

テーブルを再利用できる場合、これはさらに効率的です。

0
Jefferey Cave