123

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

例:

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

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

4

14 に答える 14

127

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

var stmt = String.format("select * from test where field in (%s)",
                         values.stream()
                         .map(v -> "?")
                         .collect(Collectors.joining(", ")));

代替使用StringBuilder(10年以上前の元の答えでした)

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

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

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

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

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

   
于 2010-06-24T03:34:13.373 に答える
64

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

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();
于 2016-04-05T08:26:55.193 に答える
11

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

于 2010-06-24T03:35:50.153 に答える
6

IN 句を使用した動的クエリで PreparedStatement を使用したくない場合は、少なくとも常に 5 変数またはそのような小さな値を下回っていることを確認してください。要素数が多いほど悪くなります(そしてひどい)。

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

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

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

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

于 2016-10-11T11:46:23.860 に答える
2

現在、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 つのクエリになります。最初のクエリ (最初の 30 パラメータ) に 3 つのバッチを追加し、次に 2 番目のクエリ (8 パラメータ) に 1 つのバッチを追加する必要があります。

于 2015-09-22T23:53:34.530 に答える
1

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

于 2010-06-24T03:37:01.740 に答える
0

多くの 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+");");

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

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

于 2013-05-30T13:41:31.957 に答える