web-dev-qa-db-fra.com

Alternatives à la clause PreparedStatement IN?

Quelles sont les meilleures solutions pour utiliser une clause SQL IN avec des instances de Java.sql.PreparedStatement, qui n'est pas prise en charge pour plusieurs valeurs en raison de problèmes de sécurité liés aux attaques par injection SQL: Un espace réservé ? représente une valeur plutôt qu'une liste de valeurs.

Considérez l'instruction SQL suivante:

SELECT my_column FROM my_table where search_column IN (?)

Utiliser preparedStatement.setString( 1, "'A', 'B', 'C'" ); est essentiellement une tentative infructueuse de contourner les raisons pour lesquelles utiliser ? en premier lieu. 

Quelles solutions de contournement sont disponibles?

313
Chris Mazzola

Une analyse des différentes options disponibles et des avantages et inconvénients de chacune d’elles est disponible ici .

Les options suggérées sont:

  • Préparez SELECT my_column FROM my_table WHERE search_column = ?, exécutez-le pour chaque valeur et UNIONnez les résultats côté client. Nécessite une seule déclaration préparée. Lente et douloureuse.
  • Préparez SELECT my_column FROM my_table WHERE search_column IN (?,?,?) et exécutez-le. Nécessite une instruction préparée par taille de la liste IN. Rapide et évident.
  • Préparez SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... et exécutez-le. [Ou utilisez UNION ALL à la place de ces points-virgules. --ed] Requiert une instruction préparée par taille de liste IN. Stupidement lent, strictement pire que WHERE search_column IN (?,?,?), je ne sais donc pas pourquoi le blogueur l’a même suggéré.
  • Utilisez une procédure stockée pour construire le jeu de résultats.
  • Préparer N requêtes de tailles différentes de listes IN; dire, avec 2, 10 et 50 valeurs. Pour rechercher une liste IN avec 6 valeurs différentes, remplissez la requête de taille 10 afin qu'elle ressemble à SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6). Tout serveur correct optimisera les valeurs en double avant d'exécuter la requête.

Aucune de ces options n'est super géniale, cependant.

On a répondu à des questions en double dans ces endroits avec des alternatives tout aussi saines, mais aucune d’elles n’est super géniale:

La bonne réponse, si vous utilisez JDBC4 et un serveur prenant en charge x = ANY(y), consiste à utiliser PreparedStatement.setArray comme décrit ici:

Cependant, il ne semble pas y avoir de moyen de faire fonctionner setArray avec les listes IN.


Parfois, les instructions SQL sont chargées au moment de l'exécution (par exemple, à partir d'un fichier de propriétés), mais nécessitent un nombre variable de paramètres. Dans ce cas, commencez par définir la requête:

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

Ensuite, chargez la requête. Déterminez ensuite le nombre de paramètres avant de l’exécuter. Une fois le nombre de paramètres connu, exécutez:

sql = any( sql, count );

Par exemple:

/**
 * 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(
            new String(new char[params]).replace("\0", "?,")
    );

    // Remove trailing comma.
    sb.setLength(Math.max(sb.length() - 1, 0));

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

Pour certaines bases de données où la transmission d'un tableau via la spécification JDBC 4 n'est pas prise en charge, cette méthode peut faciliter la transformation du = ? lent en la condition de clause IN (?) plus rapide, qui peut ensuite être développée en appelant la méthode any.

174
Dónal

Solution pour 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));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

ou

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));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}
109
Boris

Pas de manière simple, autant que je sache AFAIK . Si la cible est de garder un ratio de cache d'instructions élevé (c'est-à-dire de ne pas créer d'instruction pour chaque nombre de paramètres), vous pouvez procéder comme suit:

  1. créez une instruction avec quelques paramètres (par exemple 10):

    ... O UN IN (?,?,?,?,?,?,?,?,?,?) ...

  2. Lier tous les paramètres actuels

    setString (1, "foo"); setString (2, "bar");

  3. Lier le reste comme NULL

    setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)

NULL ne correspond jamais à rien, il est donc optimisé par le générateur de plan SQL.

La logique est facile à automatiser lorsque vous passez une liste dans une fonction DAO:

while( i < param.size() ) {
  ps.setString(i+1,param.get(i));
  i++;
}

while( i < MAX_PARAMS ) {
  ps.setNull(i+1,Types.VARCHAR);
  i++;
}
18
Vladimir Dyuzhev

Une solution de rechange désagréable, mais certainement réalisable, consiste à utiliser une requête imbriquée. Créez une table temporaire MYVALUES contenant une colonne. Insérez votre liste de valeurs dans la table MYVALUES. Puis exécuter 

select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )

Moche, mais une alternative viable si votre liste de valeurs est très grande.

Cette technique présente l’avantage supplémentaire d’optimiser potentiellement les plans de requête de l’optimiseur (rechercher plusieurs valeurs dans une page, analyser une seule fois une seule valeur par valeur, etc.) peut réduire les frais généraux si votre base de données ne met pas en cache les instructions préparées. Vos "INSERTS" doivent être exécutés par lots et la table MYVALUES doit peut-être être modifiée pour offrir un verrouillage minimal ou d’autres protections de surcharge.

10
James Schek

Les limitations de l'opérateur in () sont la racine de tout mal.

Cela fonctionne pour des cas triviaux, et vous pouvez l'étendre avec "la génération automatique de l'instruction préparée", mais elle a toujours ses limites.

  • si vous créez une instruction avec un nombre variable de paramètres, cela entraînera une surcharge d'analyse SQL à chaque appel
  • sur de nombreuses plateformes, le nombre de paramètres de l'opérateur in () est limité 
  • sur toutes les plateformes, la taille totale du texte SQL est limitée, ce qui rend impossible l'envoi de 2000 espaces réservés pour les paramètres in
  • l'envoi de variables de liaison de 1000 à 10k n'est pas possible, car le pilote JDBC a ses limites

L'approche in () peut être suffisante dans certains cas, mais pas à l'épreuve des fusées :)

La solution à la roquette consiste à transmettre le nombre arbitraire de paramètres dans un appel séparé (en transmettant un clob de paramètres, par exemple), puis à avoir une vue (ou tout autre moyen) pour les représenter en SQL et les utiliser dans votre Critères.

Une variante de force brute est ici http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

Cependant, si vous pouvez utiliser PL/SQL, ce gâchis peut devenir très soigné.

function getCustomers(in_customerIdList clob) return sys_refcursor is 
begin
    aux_in_list.parse(in_customerIdList);
    open res for
        select * 
        from   customer c,
               in_list v
        where  c.customer_id=v.token;
    return res;
end;

Ensuite, vous pouvez passer un nombre arbitraire d'identifiants client séparés par des virgules dans le paramètre et:

  • n'obtiendra aucun délai d'analyse, car le SQL pour select est stable
  • pas de complexité de fonctions en pipeline - ce n'est qu'une requête
  • le SQL utilise une jointure simple, au lieu d'un opérateur IN, ce qui est assez rapide
  • après tout, c’est une bonne règle empirique de not frapper la base de données avec un choix quelconque ou DML, puisqu’il s’agit d’Oracle, qui offre des années-lumière de plus que MySQL ou de simples moteurs de base de données similaires. PL/SQL vous permet de masquer le modèle de stockage de votre modèle de domaine d'application de manière efficace.

Le truc ici est:

  • nous avons besoin d'un appel qui accepte la chaîne longue et stocke quelque part où la session de base de données peut y accéder (par exemple, variable de package simple ou dbms_session.set_context)
  • alors nous avons besoin d'une vue qui peut analyser cela en lignes
  • et puis vous avez une vue qui contient les identifiants que vous interrogez, de sorte que tout ce dont vous avez besoin est une simple jointure à la table interrogée.

La vue ressemble à:

create or replace view in_list
as
select
    trim( substr (txt,
          instr (txt, ',', 1, level  ) + 1,
          instr (txt, ',', 1, level+1)
             - instr (txt, ',', 1, level) -1 ) ) as token
    from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1

où aux_in_list.getpayload fait référence à la chaîne d'entrée d'origine.


Une approche possible serait de passer des tableaux pl/sql (pris en charge par Oracle uniquement). Toutefois, vous ne pouvez pas les utiliser en SQL pur. Par conséquent, une étape de conversion est toujours nécessaire. La conversion ne peut pas être effectuée en SQL, alors après tout, la solution la plus efficace consiste à passer un clob avec tous les paramètres de chaîne et à le convertir dans une vue.

8
Gee Bee

Je ne l'ai jamais essayé, mais .setArray () ferait ce que vous cherchez?

Mise à jour : Evidemment non. setArray ne semble fonctionner qu'avec un fichier Java.sql.Array provenant d'une colonne ARRAY extraite d'une requête précédente ou d'une sous-requête avec une colonne ARRAY.

5
Paul Tomblin

Voici comment je l'ai résolu dans ma propre application. Idéalement, vous devriez utiliser un StringBuilder au lieu de + pour les chaînes.

    String inParenthesis = "(?";
    for(int i = 1;i < myList.size();i++) {
      inParenthesis += ", ?";
    }
    inParenthesis += ")";

    try(PreparedStatement statement = SQLite.connection.prepareStatement(
        String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
      int x = 1;
      statement.setLong(x++, race.startTime);
      statement.setString(x++, race.name);
      statement.setInt(x++, traderIdx);

      for(String str : race.betFair.winners) {
        statement.setString(x++, str);
      }

      int effected = statement.executeUpdate();
    }

L'utilisation d'une variable comme x ci-dessus au lieu de nombres concrets aide beaucoup si vous décidez de modifier la requête ultérieurement.

5
m.sabouri

Ma solution est la suivante:

create or replace type split_tbl as table of varchar(32767);
/

create or replace function split
(
  p_list varchar2,
  p_del varchar2 := ','
) return split_tbl pipelined
is
  l_idx    pls_integer;
  l_list    varchar2(32767) := p_list;
  l_value    varchar2(32767);
begin
  loop
    l_idx := instr(l_list,p_del);
    if l_idx > 0 then
      pipe row(substr(l_list,1,l_idx-1));
      l_list := substr(l_list,l_idx+length(p_del));
    else
      pipe row(l_list);
      exit;
    end if;
  end loop;
  return;
end split;
/

Maintenant, vous pouvez utiliser une variable pour obtenir des valeurs dans une table:

select * from table(split('one,two,three'))
  one
  two
  three

select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
  value1 AAA
  value2 BBB

Ainsi, la déclaration préparée pourrait être:

  "select * from TABLE where COL in (select * from table(split(?)))"

Cordialement,

Javier Ibanez

5
Javier Ibanez

Je suppose que vous pourriez (en utilisant une manipulation de chaîne de base) générer la chaîne de requête dans la variable PreparedStatement pour avoir un nombre de ? correspondant au nombre d'éléments de votre liste. 

Bien sûr, si vous faites cela, vous êtes sur le point de générer une chaîne OR géante dans votre requête, mais sans avoir le nombre correct de ? dans la chaîne de requête, je ne vois pas comment vous pourriez autrement contourner ce problème. .

3
Adam Bellaire

Vous pouvez utiliser la méthode setArray mentionnée dans 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();
2
Panky031

Spring autorise le passage de Java.util.Lists à NamedParameterJdbcTemplate , qui automatise la génération de (?,?,?, ...,?), En fonction du nombre d’arguments.

Pour Oracle, cet article de blog traite de l'utilisation de Oracle.sql.ARRAY (Connection.createArrayOf ne fonctionne pas avec Oracle). Pour cela, vous devez modifier votre instruction SQL:

SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))

La fonction de table Oracle transforme le tableau transmis en une table semblable à la valeur utilisable dans l'instruction IN.

1

Je suis tombé sur un certain nombre de limitations liées à la déclaration préparée:

  1. Les instructions préparées sont mises en cache uniquement dans la même session (Postgres), de sorte que cela ne fonctionnera réellement qu'avec le regroupement de connexions.
  2. Un grand nombre d'instructions préparées différentes, proposées par @BalusC, peut entraîner un remplissage excessif du cache et des instructions précédemment mises en cache seront supprimées.
  3. La requête doit être optimisée et utiliser des index. Cela semble évident, par exemple l'instruction ANY (ARRAY ...) proposée par @Boris dans l'une des réponses les plus fréquentes ne peut pas utiliser d'index et la requête sera lente malgré la mise en cache
  4. L'instruction préparée met également en cache le plan de requête et les valeurs réelles des paramètres spécifiés dans l'instruction ne sont pas disponibles.

Parmi les solutions proposées, je choisirais celle qui n'affaiblit pas les performances de la requête et génère le moins de requêtes possible. Ce sera le n ° 4 (regroupant quelques requêtes) à partir du lien @Don ou spécifiant des valeurs NULL pour les '?' Inutiles. marques proposées par @Vladimir Dyuzhev 

1
Alexander

à la place d'utiliser

SELECT my_column FROM my_table where search_column IN (?)

utiliser la déclaration SQL comme 

select id, name from users where id in (?, ?, ?)

et

preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');

ou utiliser une procédure stockée, ce serait la meilleure solution, car les instructions SQL seront compilées et stockées sur le serveur DataBase.

1
kapil das

Sormula prend en charge l'opérateur SQL IN en vous permettant de fournir un objet Java.util.Collection en tant que paramètre. Il crée une déclaration préparée avec un? pour chacun des éléments de la collection. Voir Exemple 4 (SQL dans l'exemple est un commentaire pour clarifier ce qui est créé mais qui n'est pas utilisé par Sormula).

1
Jeff Miller

essayez d'utiliser la fonction instr?

select my_column from my_table where  instr(?, ','||search_column||',') > 0

puis

ps.setString(1, ",A,B,C,"); 

Certes, il s’agit d’un sale tour de main, mais cela réduit les possibilités d’injection SQL. Fonctionne dans Oracle quand même.

1
stjohnroe

Voici une solution complète en Java pour créer l’instruction préparée pour vous:

/*usage:

Util u = new Util(500); //500 items per bracket. 
String sqlBefore  = "select * from myTable where (";
List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5)); 
string sqlAfter = ") and foo = 'bar'"; 

PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId");
*/



import Java.sql.Connection;
import Java.sql.PreparedStatement;
import Java.sql.SQLException;
import Java.util.ArrayList;
import Java.util.List;

public class Util {

    private int numValuesInClause;

    public Util(int numValuesInClause) {
        super();
        this.numValuesInClause = numValuesInClause;
    }

    public int getNumValuesInClause() {
        return numValuesInClause;
    }

    public void setNumValuesInClause(int numValuesInClause) {
        this.numValuesInClause = numValuesInClause;
    }

    /** Split a given list into a list of lists for the given size of numValuesInClause*/
    public List<List<Integer>> splitList(
            List<Integer> values) {


        List<List<Integer>> newList = new ArrayList<List<Integer>>(); 
        while (values.size() > numValuesInClause) {
            List<Integer> sublist = values.subList(0,numValuesInClause);
            List<Integer> values2 = values.subList(numValuesInClause, values.size());   
            values = values2; 

            newList.add( sublist);
        }
        newList.add(values);

        return newList;
    }

    /**
     * Generates a series of split out in clause statements. 
     * @param sqlBefore ""select * from dual where ("
     * @param values [1,2,3,4,5,6,7,8,9,10]
     * @param "sqlAfter ) and id = 5"
     * @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)"
     */
    public String genInClauseSql(String sqlBefore, List<Integer> values,
            String sqlAfter, String identifier) 
    {
        List<List<Integer>> newLists = splitList(values);
        String stmt = sqlBefore;

        /* now generate the in clause for each list */
        int j = 0; /* keep track of list:newLists index */
        for (List<Integer> list : newLists) {
            stmt = stmt + identifier +" in (";
            StringBuilder innerBuilder = new StringBuilder();

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



            String inClause = innerBuilder.deleteCharAt(
                    innerBuilder.length() - 1).toString();

            stmt = stmt + inClause;
            stmt = stmt + ")";


            if (++j < newLists.size()) {
                stmt = stmt + " OR ";
            }

        }

        stmt = stmt + sqlAfter;
        return stmt;
    }

    /**
     * Method to convert your SQL and a list of ID into a safe prepared
     * statements
     * 
     * @throws SQLException
     */
    public PreparedStatement prepareStatements(String sqlBefore,
            ArrayList<Integer> values, String sqlAfter, Connection c, String identifier)
            throws SQLException {

        /* First split our potentially big list into lots of lists */
        String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier);
        PreparedStatement ps = c.prepareStatement(stmt);

        int i = 1;
        for (int val : values)
        {

            ps.setInt(i++, val);

        }
        return ps;

    }

}
1
dwjohnston

SetArray est la meilleure solution, mais n'est pas disponible pour de nombreux pilotes plus anciens. La solution de contournement suivante peut être utilisée dans Java8

String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)"

String markersString = inputArray.stream().map(e -> "?").collect(joining(","));
String sqlQuery = String.format(baseSQL, markersString);

//Now create Prepared Statement and use loop to Set entries
int index=1;

for (String input : inputArray) {
     preparedStatement.setString(index++, input);
}

Cette solution est meilleure que les autres solutions de boucles while laid où la chaîne de requête est construite par itérations manuelles 

0
Raheel

Vous pouvez utiliser Collections.nCopies pour générer une collection d'espaces réservés et les joindre à l'aide de String.join:

List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try (   Connection connection = getConnection();
        PreparedStatement ps = connection.prepareStatement(sql)) {
    int i = 1;
    for (String param : params) {
        ps.setString(i++, param);
    }
    /*
     * Execute query/do stuff
     */
}
0
Gurwinder Singh

Générez la chaîne de requête dans PreparedStatement de manière à ce qu'un nombre de? Correspond au nombre d'éléments de votre liste. Voici un exemple:

public void myQuery(List<String> items, int other) {
  ...
  String q4in = generateQsForIn(items.size());
  String sql = "select * from stuff where foo in ( " + q4in + " ) and bar = ?";
  PreparedStatement ps = connection.prepareStatement(sql);
  int i = 1;
  for (String item : items) {
    ps.setString(i++, item);
  }
  ps.setInt(i++, other);
  ResultSet rs = ps.executeQuery();
  ...
}

private String generateQsForIn(int numQs) {
    String items = "";
    for (int i = 0; i < numQs; i++) {
        if (i != 0) items += ", ";
        items += "?";
    }
    return items;
}
0
neu242

PreparedStatement ne fournit aucun bon moyen de traiter la clause SQL IN. Per http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Vous ne pouvez pas remplacer par des éléments destinés à faire partie de l'instruction SQL. Cela est nécessaire car si le code SQL lui-même peut changer, le pilote ne peut pas précompiler l’instruction. Cela a également pour effet secondaire de prévenir les attaques par injection SQL. " J'ai fini par utiliser l'approche suivante:

String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)";
query = query.replace("$searchColumns", "'A', 'B', 'C'");
Statement stmt = connection.createStatement();
boolean hasResults = stmt.execute(query);
do {
    if (hasResults)
        return stmt.getResultSet();

    hasResults = stmt.getMoreResults();

} while (hasResults || stmt.getUpdateCount() != -1);
0
pedram bashiri

Juste pour être complet et parce que je n'ai vu personne le suggérer:

Avant d'implémenter l'une des suggestions compliquées ci-dessus, déterminez si l'injection SQL pose effectivement un problème dans votre scénario. 

Dans de nombreux cas, la valeur fournie à IN (...) est une liste d'identifiants générés de manière à garantir qu'aucune injection n'est possible ... (par exemple, les résultats d'une sélection précédente, some_id de some_table où un_condition.)

Si tel est le cas, vous pouvez concaténer cette valeur et ne pas utiliser les services ou l'instruction préparée à cet effet, ni les utiliser pour d'autres paramètres de cette requête. 

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
0
epeleg

Regexp peut aider dans certaines situations. Voici un exemple que j'ai vérifié sur Oracle, et cela fonctionne. 

select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')

Mais il y a un certain nombre d'inconvénients:

  1. Toute colonne appliquée doit être convertie en varchar/char, au moins implicitement.
  2. Il faut faire attention avec les caractères spéciaux.
  3. Cela peut ralentir les performances - dans mon cas, la version IN utilise l'analyse d'index et de plages et la version de REGEXP effectue une analyse complète.
0
Vasili

Après avoir examiné diverses solutions dans différents forums et ne pas avoir trouvé de bonne solution, je pense que le hack ci-dessous que j'ai proposé est le plus facile à suivre et à coder:

Exemple: supposons que vous ayez plusieurs paramètres à transmettre dans la clause 'IN'. Il suffit de mettre une chaîne factice dans la clause 'IN', par exemple, "PARAM" désigne la liste des paramètres qui viendront à la place de cette chaîne factice.

    select * from TABLE_A where ATTR IN (PARAM);

Vous pouvez collecter tous les paramètres dans une seule variable String dans votre code Java. Cela peut être fait comme suit:

    String param1 = "X";
    String param2 = "Y";
    String param1 = param1.append(",").append(param2);

Vous pouvez ajouter tous vos paramètres séparés par des virgules dans une seule variable String, 'param1', dans notre cas.

Après avoir collecté tous les paramètres dans une seule chaîne, vous pouvez simplement remplacer le texte factice de votre requête, c'est-à-dire "PARAM" dans ce cas, avec le paramètre String, c'est-à-dire, param1. Voici ce que tu dois faire:

    String query = query.replaceFirst("PARAM",param1); where we have the value of query as 

    query = "select * from TABLE_A where ATTR IN (PARAM)";

Vous pouvez maintenant exécuter votre requête en utilisant la méthode executeQuery (). Assurez-vous simplement que le mot "PARAM" ne figure nulle part dans votre requête. Vous pouvez utiliser une combinaison de caractères spéciaux et d’alphabets à la place du mot "PARAM" afin de vous assurer qu’il n’ya aucune possibilité qu’un tel mot vienne dans la requête. J'espère que vous avez la solution.

Remarque: Bien qu'il ne s'agisse pas d'une requête préparée, elle effectue le travail que je voulais que mon code fasse.

0
bnsk

Il existe différentes approches alternatives que nous pouvons utiliser pour la clause IN dans PreparedStatement.

  1. Utilisation de requêtes simples: performances les plus lentes et ressources importantes
  2. Utilisation de StoredProcedure - Le plus rapide mais spécifique à la base de données
  3. Création d'une requête dynamique pour PreparedStatement - Good Performance, mais ne tire pas parti de la mise en cache et PreparedStatement est recompilé à chaque fois.
  4. Utiliser NULL dans les requêtes PreparedStatement - Performances optimales, fonctionne très bien lorsque vous connaissez la limite d'arguments de clause IN. S'il n'y a pas de limite, vous pouvez alors exécuter des requêtes par lots. Exemple d’extrait de code:

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

Vous pouvez vérifier plus de détails sur ces approches alternatives ici .

0
Pankaj

Suivre l'idée d'Adam. Faites en sorte que votre instruction préparée sorte en quelque sorte de my_column dans my_table où search_column dans (#) Créez une chaîne x et remplissez-la avec le nombre "?,?,?" en fonction de votre liste de valeurs Ensuite, changez simplement le # dans la requête pour votre nouveau String x an populate

0
Yorch

Je viens de mettre au point une option spécifique à PostgreSQL pour cela. C'est un peu un bidouillage, et présente ses propres avantages, inconvénients et limitations, mais il semble fonctionner et ne se limite pas à un langage de développement, une plate-forme ou un pilote PG spécifique.

L'astuce consiste bien sûr à trouver un moyen de transmettre un ensemble de valeurs de longueur arbitraire en tant que paramètre unique et à le faire reconnaître par la base de données sous forme de valeurs multiples. La solution que je travaille est de construire une chaîne délimitée à partir des valeurs de la collection, de transmettre cette chaîne en tant que paramètre unique et d’utiliser string_to_array () avec la conversion requise pour que PostgreSQL puisse l’utiliser correctement.

Donc, si vous voulez chercher "foo", "blah" et "abc", vous pouvez les concaténer ensemble en une seule chaîne, comme suit: "foo, blah, abc". Voici le SQL droit:

select column from table
where search_column = any (string_to_array('foo,blah,abc', ',')::text[]);

Vous devez évidemment changer la distribution explicite en ce que vous voulez que votre tableau de valeurs résultant soit - int, text, uuid, etc. Et parce que la fonction prend une valeur de chaîne unique (ou deux, je suppose, si vous souhaitez personnaliser le délimiteur également), vous pouvez le passer en tant que paramètre dans une instruction préparée:

select column from table
where search_column = any (string_to_array($1, ',')::text[]);

C'est même assez flexible pour supporter des choses comme les comparaisons LIKE:

select column from table
where search_column like any (string_to_array('foo%,blah%,abc%', ',')::text[]);

Encore une fois, il ne fait aucun doute que c'est un bidouillage, mais cela fonctionne et vous permet d'utiliser des instructions préparées pré-compilées qui prennent * ahem * paramètres discrets, avec les avantages de sécurité et (peut-être) de performance correspondants. Est-ce conseillé et réellement performant? Naturellement, cela dépend, car vous avez l’analyse des chaînes et éventuellement la conversion en cours avant même que votre requête ne soit exécutée. Si vous vous attendez à envoyer trois, cinq, quelques dizaines de valeurs, c'est sûr que c'est bon. Quelques milliers? Ouais, peut-être pas tellement. YMMV, limitations et exclusions applicables, aucune garantie, expresse ou implicite.

Mais ça marche.

0
Joel Fouse

Par souci d’exhaustivité: tant que l’ensemble de valeurs n’est pas trop volumineux, vous pourriez aussi simplement construire une instruction telle que

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

que vous pourriez ensuite passer à prepare (), puis utiliser setXXX () dans une boucle pour définir toutes les valeurs. Cela semble dégoûtant, mais de nombreux "gros" systèmes commerciaux font ce genre de choses régulièrement jusqu'à atteindre des limites spécifiques à la base de données, telles que 32 Ko (je pense que c'est le cas) pour les déclarations dans Oracle.

Bien sûr, vous devez vous assurer que l'ensemble ne sera jamais déraisonnablement volumineux, ou éviter les erreurs si cela se produit.

0
Carl Smotricz