web-dev-qa-db-fra.com

PreparedStatement avec liste de paramètres dans une clause IN

Comment définir la valeur de la clause in dans un PreparedStatement dans JDBC lors de l'exécution d'une requête.

Exemple:

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

Si cet article peut contenir plusieurs valeurs, comment puis-je le faire? Parfois, je connais la liste des paramètres à l'avance ou parfois, je ne le sais pas à l'avance. Comment gérer ce cas?

70
Harish

Ce que je fais est d'ajouter un "?" pour chaque valeur possible. 

Par exemple:

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 = ... 

Et puis, avec bonheur, configurez les paramètres

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

Vous pouvez utiliser la méthode setArray comme indiqué dans le javadoc ci-dessous:

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

Code:

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();
43
madx

Vous voudrez peut-être vérifier ce lien:

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

Il explique les avantages et les inconvénients des différentes méthodes de création de la clause PreparedStatement avec in.

MODIFIER:

Une approche évidente consiste à générer dynamiquement le "?" partie au moment de l'exécution, mais je ne veux pas simplement suggérer cette approche car, selon l'utilisation que vous en ferez, elle pourrait être inefficace (puisque PreparedStatement devra être 'compilé' chaque fois qu'il est utilisé)

14
ryanprayogo

Vous ne pouvez pas remplacer ? dans votre requête par un nombre arbitraire de valeurs. Chaque ? est un espace réservé pour une seule valeur. Pour prendre en charge un nombre arbitraire de valeurs, vous devez créer de manière dynamique une chaîne contenant ?, ?, ?, ... , ?, le nombre de points d'interrogation correspondant au nombre de valeurs que vous souhaitez dans la clause in.

7
Asaph

Vous avez besoin de jdbc4, alors vous pouvez utiliser setArray!

Dans mon cas, cela n'a pas fonctionné, car le type de données UUID dans postgres semble avoir encore ses points faibles, mais il fonctionne pour les types habituels.

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

Bien sûr, remplacez $ VALUETYPE et myValuesAsArray par les valeurs correctes.

Remarquez le commentaire suivant de Marks:

Votre base de données et le pilote doivent le supporter! J'ai essayé Postgres 9.4 mais je pense que cela a été introduit plus tôt. Vous avez besoin d’un pilote jdbc 4, sinon setArray ne sera pas disponible. J'ai utilisé le pilote postgresql 9.4-1201-jdbc41 livré avec une botte à ressort

3

Vous ne voulez pas utiliser PreparedStatment avec des requêtes dynamiques en utilisant la clause IN, du moins, assurez-vous que vous avez toujours moins de 5 variables ou une petite valeur comme ça, mais même comme ça, je pense que c'est une mauvaise idée (pas terrible, mais mauvaise). Comme le nombre d'éléments est grand, ce sera pire (et terrible). 

Imaginez cent ou mille possibilités dans votre clause IN:

  1. C'est contre-productif, vous perdez des performances et de la mémoire car vous mettez en cache à chaque fois une nouvelle demande, et PreparedStatement ne concerne pas uniquement l'injection SQL, il s'agit de performances. Dans ce cas, Statement est mieux.

  2. Votre piscine a une limite PreparedStatment (-1 par défaut, mais vous devez la limiter) et vous atteindrez cette limite! et si vous n'avez pas de limite ou une limite très grande, vous courez un risque de fuite de mémoire et, dans les cas extrêmes, d'erreurs OutofMemory. Donc, si c'est pour votre petit projet personnel utilisé par 3 utilisateurs, ce n'est pas spectaculaire, mais vous ne le souhaitez pas si vous êtes dans une grande entreprise et que votre application est utilisée par des milliers de personnes et des millions de requêtes.

Quelques lectures . IBM: Considérations relatives à l’utilisation de la mémoire lors de l’utilisation de la mise en cache des instructions préparées

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
Gaurav vijayvargiya

Ce que vous pouvez faire est de construire dynamiquement la chaîne de sélection (la partie 'IN (?)') Par une simple boucle for dès que vous savez le nombre de valeurs à mettre dans la clause IN. Vous pouvez ensuite instancier le PreparedStatement.

2
rfk

Actuellement, MySQL ne permet pas de définir plusieurs valeurs dans un seul appel de méthode . Vous devez donc l'avoir sous votre propre contrôle. Je crée généralement une instruction préparée pour un nombre prédéfini de paramètres, puis j'ajoute autant de lots que nécessaire.

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

Si vous n'aimez pas définir NULL lorsqu'il ne reste plus de paramètres, vous pouvez modifier le code pour générer deux requêtes et deux instructions préparées. La première est la même, mais la deuxième déclaration pour le reste (module) . Dans cet exemple particulier, il s'agirait d'une requête pour 10 paramètres et d'une pour 8 paramètres. Vous devrez ajouter 3 lots pour la première requête (30 premiers paramètres), puis un lot pour la deuxième requête (8 paramètres).

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

Vous pouvez utiliser :

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

Ensuite :

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

Exemple:

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

essayez avec ce code 

 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

De nombreuses bases de données ont le concept de table temporaire. Même si vous n’avez pas de table temporaire, vous pouvez toujours en générer une avec un nom unique et l’abandonner lorsque vous avez terminé. Bien que la création et l’abandon d’une table entraînent une surcharge, cela peut être raisonnable pour des opérations très volumineuses ou lorsque vous utilisez la base de données en tant que fichier local ou en mémoire (SQLite).

Un exemple de quelque chose que je suis au milieu de (en utilisant 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+");");

Notez que les champs utilisés par ma table sont créés dynamiquement.

Cela serait encore plus efficace si vous pouviez réutiliser la table.

0
Jefferey Cave