Quelles sont les meilleures solutions de contournement pour utiliser une IN
clause SQL 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é d'attaque 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 (?)
L'utilisation preparedStatement.setString( 1, "'A', 'B', 'C'" );
est essentiellement une tentative non fonctionnelle de contourner les raisons de l'utilisation ?
en premier lieu.
Quelles solutions de contournement sont disponibles?
Réponses:
Une analyse des différentes options disponibles et les avantages et inconvénients de chacune sont disponibles ici .
Les options suggérées sont:
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.SELECT my_column FROM my_table WHERE search_column IN (?,?,?)
et exécutez-le. Nécessite une instruction préparée par taille de liste IN. Rapide et évident.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
et exécutez-le. [Ou utilisezUNION ALL
à la place de ces points-virgules. --ed] Nécessite une instruction préparée par taille de liste IN. Bêtement lent, strictement pire queWHERE search_column IN (?,?,?)
, donc je ne sais pas pourquoi le blogueur l'a même suggéré.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
. Tout serveur décent optimisera les valeurs en double avant d'exécuter la requête.Cependant, aucune de ces options n'est super géniale.
Des réponses ont été apportées à ces questions en double avec des alternatives tout aussi sensées, mais aucune d'entre elles n'est super géniale:
La bonne réponse, si vous utilisez JDBC4 et un serveur qui prend en charge
x = ANY(y)
, est d'utiliserPreparedStatement.setArray
comme décrit ici:Cependant, il ne semble pas y avoir de moyen de
setArray
travailler avec les listes IN.Parfois, les instructions SQL sont chargées lors de l'exécution (par exemple, à partir d'un fichier de propriétés) mais nécessitent un nombre variable de paramètres. Dans de tels cas, définissez d'abord la requête:
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:
Par exemple:
Pour certaines bases de données où le passage d'un tableau via la spécification JDBC 4 n'est pas pris en charge, cette méthode peut faciliter la transformation de la condition slow
= ?
enIN (?)
clause clause plus rapide , qui peut ensuite être développée en appelant laany
méthode.la source
Solution pour PostgreSQL:
ou
la source
.createArrayOf()
partie, mais je ne suis pas sûr que la sémantique stricte pour les utilisateursArray
soit définie par la spécification JDBC..createArrayOf
cela ne fonctionne pas, vous pouvez créer votre propre création manuelle de littéral de tableau commeString arrayLiteral = "{A,\"B \", C,D}"
(notez que "B" a un espace tandis que C ne fonctionne pas) et ensuitestatement.setString(1,arrayLiteral)
où l'instruction préparée est... IN (SELECT UNNEST(?::VARCHAR[]))
ou... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PS: je ne pense pas que celaANY
fonctionne avec aSELECT
.)Pas de manière simple AFAIK. Si l'objectif est de maintenir un rapport 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:
créer une instruction avec quelques paramètres (par exemple 10):
... O A UN DANS (?,?,?,?,?,?,?,?,?,?) ...
Lier tous les paramètres actuall
setString (1, "foo"); setString (2, "bar");
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:
la source
NULL
La requête correspondrait-elle à uneNULL
valeur dans la base de données?NOT IN
etIN
ne gérez pas les null de la même manière. Exécutez ceci et voyez ce qui se passe:select 'Matched' as did_it_match where 1 not in (5, null);
Retirez ensuite lenull
et observez la magie.a IN (1,2,3,3,3,3,3)
la même chose quea IN (1,2,3)
. Il fonctionne également avecNOT IN
contrairement àa NOT IN (1,2,3,null,null,null,null)
(qui ne renvoie toujours aucune ligne car ilany_value != NULL
est toujours faux).Une solution de contournement désagréable, mais certainement réalisable consiste à utiliser une requête imbriquée. Créez une table temporaire MYVALUES avec une colonne dedans. Insérez votre liste de valeurs dans la table MYVALUES. Ensuite, exécutez
Moche, mais une alternative viable si votre liste de valeurs est très longue.
Cette technique présente l'avantage supplémentaire de plans de requête potentiellement meilleurs de l'optimiseur (vérifier une page pour plusieurs valeurs, tablescan une seule fois au lieu d'une fois par valeur, etc.) peut économiser sur la surcharge si votre base de données ne met pas en cache les instructions préparées. Vos "INSERTS" devront être effectués en lot et la table MYVALUES devra peut-être être modifiée pour avoir un verrouillage minimal ou d'autres protections élevées.
la source
Les limitations de l'opérateur in () sont la racine de tout mal.
Cela fonctionne pour les 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.
L'approche in () peut être assez bonne dans certains cas, mais pas à l'épreuve des fusées :)
La solution à l'épreuve des fusées consiste à transmettre le nombre arbitraire de paramètres dans un appel distinct (en passant un bloc de paramètres, par exemple), puis à avoir une vue (ou toute autre manière) pour les représenter en SQL et les utiliser dans votre où 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 désordre peut devenir assez soigné.
Ensuite, vous pouvez passer un nombre arbitraire d'ID de client séparés par des virgules dans le paramètre et:
L'astuce est la suivante:
La vue ressemble à:
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), mais vous ne pouvez pas les utiliser en SQL pur, donc une étape de conversion est toujours nécessaire. La conversion ne peut pas être effectuée en SQL, donc après tout, passer un clob avec tous les paramètres dans une chaîne et le convertir dans une vue est la solution la plus efficace.
la source
Voici comment je l'ai résolu dans ma propre application. Idéalement, vous devriez utiliser un StringBuilder au lieu d'utiliser + pour les chaînes.
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.
la source
Je ne l'ai jamais essayé, mais .setArray () ferait-il ce que vous cherchez?
Mise à jour : Evidemment non. setArray ne semble fonctionner qu'avec un java.sql.Array provenant d'une colonne ARRAY que vous avez récupérée d'une requête précédente, ou d'une sous-requête avec une colonne ARRAY.
la source
Ma solution est la suivante:
Vous pouvez maintenant utiliser une variable pour obtenir des valeurs dans une table:
Ainsi, la déclaration préparée pourrait être:
Cordialement,
Javier Ibanez
la source
Je suppose que vous pourriez (en utilisant la manipulation de chaîne de base) générer la chaîne de requête dans le
PreparedStatement
pour avoir un certain nombre de?
correspondant au nombre d'éléments dans votre liste.Bien sûr, si vous faites cela, vous n'êtes qu'à un pas de générer un géant enchaîné
OR
dans votre requête, mais sans avoir le bon nombre de?
dans la chaîne de requête, je ne vois pas comment vous pouvez contourner cela.la source
Vous pouvez utiliser la méthode setArray comme mentionné dans ce javadoc :
la source
Vous pouvez utiliser
Collections.nCopies
pour générer une collection d'espaces réservés et les rejoindre en utilisantString.join
:la source
Voici une solution complète en Java pour créer l'instruction préparée pour vous:
la source
Spring permet de transmettre java.util.Lists à NamedParameterJdbcTemplate , qui automatise la génération de (?,?,?, ...,?), En fonction du nombre d'arguments.
Pour Oracle, cette publication 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:
La fonction de table Oracle transforme le tableau passé en une table similaire à une valeur utilisable dans l'
IN
instruction.la source
essayez d'utiliser la fonction instr?
puis
Certes, c'est un peu un hack sale, mais cela réduit les possibilités d'injection SQL. Fonctionne de toute façon dans Oracle.
la source
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 l' exemple 4 (SQL dans l'exemple est un commentaire pour clarifier ce qui est créé mais n'est pas utilisé par Sormula).
la source
à la place d'utiliser
utiliser la déclaration SQL comme
et
ou utiliser une procédure stockée, ce serait la meilleure solution, car les instructions sql seront compilées et stockées dans le serveur DataBase
la source
J'ai rencontré un certain nombre de limitations liées à la déclaration préparée:
Parmi les solutions proposées, je choisirais celle qui ne diminue pas les performances des requêtes et fait le moins de requêtes. Ce sera le # 4 (regroupant quelques requêtes) du lien @Don ou en spécifiant des valeurs NULL pour les «?» Inutiles marques proposées par @Vladimir Dyuzhev
la source
Je viens de trouver une option spécifique à PostgreSQL pour cela. C'est un peu un hack, et il a ses propres avantages, inconvénients et limitations, mais il semble fonctionner et n'est pas limité à un langage de développement, une plate-forme ou un pilote PG spécifique.
L'astuce est bien sûr de trouver un moyen de passer une collection de valeurs de longueur arbitraire en tant que paramètre unique, et de faire en sorte que la base de données la reconnaisse comme plusieurs valeurs. La solution que je travaille est de construire une chaîne délimitée à partir des valeurs de la collection, de passer cette chaîne en tant que paramètre unique et d'utiliser string_to_array () avec le casting requis pour que PostgreSQL l'utilise correctement.
Donc, si vous voulez rechercher "foo", "blah" et "abc", vous pouvez les concaténer ensemble dans une seule chaîne comme: "foo, blah, abc". Voici le SQL simple:
Vous changeriez évidemment la distribution explicite en ce que vous vouliez que votre tableau de valeurs résultant soit - int, texte, uuid, etc. Et parce que la fonction prend une seule valeur de chaîne (ou deux je suppose, si vous voulez personnaliser le délimiteur ainsi), vous pouvez le passer comme paramètre dans une instruction préparée:
C'est même assez flexible pour supporter des choses comme les comparaisons LIKE:
Encore une fois, il ne fait aucun doute que c'est un hack, mais cela fonctionne et vous permet toujours d'utiliser des instructions préparées précompilées qui prennent * ahem * des paramètres discrets , avec les avantages de sécurité et (peut-être) de performance qui l'accompagnent. Est-il conseillé et réellement performant? Naturellement, cela dépend, car vous avez une analyse de chaîne et éventuellement un cast avant que votre requête ne s'exécute. Si vous vous attendez à envoyer trois, cinq, quelques dizaines de valeurs, bien sûr, c'est très bien. Quelques milliers? Ouais, peut-être pas tellement. YMMV, des limitations et exclusions s'appliquent, aucune garantie expresse ou implicite.
Mais ça marche.
la source
Juste pour être complet: tant que l'ensemble de valeurs n'est pas trop grand, vous pouvez également simplement construire une instruction de type chaîne comme
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 régulièrement ce genre de choses jusqu'à ce qu'ils atteignent des limites spécifiques aux bases de données, comme 32 Ko (je pense que c'est le cas) pour les instructions dans Oracle.
Bien sûr, vous devez vous assurer que l'ensemble ne sera jamais déraisonnablement volumineux, ou faire un piégeage d'erreurs dans le cas où il l'est.
la source
Suivant l'idée d'Adam. Faites votre instruction préparée sorte de sélectionner my_column de my_table où search_column dans (#) Créez une chaîne x et remplissez-la avec un certain nombre de "?,?,?" en fonction de votre liste de valeurs Ensuite, changez simplement le # dans la requête pour votre nouvelle chaîne x un peuplement
la source
Générez la chaîne de requête dans le PreparedStatement pour avoir un nombre de? Correspondant au nombre d'éléments dans votre liste. Voici un exemple:
la source
StringBuilder
. Mais pas comme vous le pensez. En décompilant,generateQsForIn
vous pouvez voir que par itération de boucle, deux nouveauxStringBuilder
sont alloués ettoString
sont appelés sur chacun. L'StringBuilder
optimisation n'attrape que des choses comme,"x" + i+ "y" + j
mais ne s'étend pas au-delà d'une expression.ps.setObject(1,items)
au lieu d'itérer sur la liste puis de définir leparamteres
?Il existe différentes approches alternatives que nous pouvons utiliser pour la clause IN dans PreparedStatement.
Utilisez NULL dans les requêtes PreparedStatement - Performances optimales, fonctionne très bien lorsque vous connaissez la limite des arguments de la clause IN. S'il n'y a pas de limite, vous pouvez exécuter des requêtes par lots. Un exemple d'extrait de code est;
Vous pouvez vérifier plus de détails sur ces approches alternatives ici .
la source
Dans certaines situations, l'expression rationnelle peut être utile. Voici un exemple que j'ai vérifié sur Oracle, et cela fonctionne.
Mais il présente un certain nombre d'inconvénients:
la source
Après avoir examiné diverses solutions dans différents forums et ne pas avoir trouvé une bonne solution, je pense que le hack ci-dessous que j'ai trouvé est le plus facile à suivre et à coder:
Exemple: supposons que vous ayez plusieurs paramètres à passer dans la clause 'IN'. Il suffit de mettre une chaîne factice à l'intérieur de la clause 'IN', par exemple, "PARAM" indique la liste des paramètres qui viendront à la place de cette chaîne factice.
Vous pouvez collecter tous les paramètres dans une seule variable String dans votre code Java. Cela peut être fait comme suit:
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 dans votre requête, c'est-à-dire "PARAM" dans ce cas, par le paramètre String, c'est-à-dire param1. Voici ce que tu dois faire:
Vous pouvez maintenant exécuter votre requête à l'aide de 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 au lieu du mot "PARAM" afin de vous assurer qu'il n'y a aucune possibilité qu'un tel mot apparaisse dans la requête. J'espère que vous avez la solution.
Remarque: Bien que ce ne soit pas une requête préparée, elle fait le travail que je voulais que mon code fasse.
la source
Juste pour être complet et parce que je n'ai vu personne d'autre le suggérer:
Avant de mettre en œuvre l'une des suggestions compliquées ci-dessus, vérifiez si l'injection SQL est effectivement un problème dans votre scénario.
Dans de nombreux cas, la valeur fournie à IN (...) est une liste d'ID qui ont été générés de manière à être sûr qu'aucune injection n'est possible ... (par exemple, les résultats d'un précédent select some_id from some_table où une_condition.)
Si tel est le cas, vous pouvez simplement concaténer cette valeur et ne pas utiliser les services ou l'instruction préparée pour cela ou les utiliser pour d'autres paramètres de cette requête.
la source
PreparedStatement ne fournit aucun bon moyen de traiter la clause SQL IN. Par http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Vous ne pouvez pas substituer des éléments destinés à faire partie de l'instruction SQL. Cela est nécessaire car si le SQL lui-même peut changer, le le pilote ne peut pas précompiler l'instruction. Il a également pour effet secondaire d'empêcher les attaques par injection SQL. " J'ai fini par utiliser l'approche suivante:
la source
SetArray est la meilleure solution mais elle n'est pas disponible pour de nombreux pilotes plus anciens. La solution de contournement suivante peut être utilisée dans java8
Cette solution est meilleure que les autres solutions laides tout en boucle où la chaîne de requête est construite par itérations manuelles
la source
Cela a fonctionné pour moi (psuedocode):
spécifiez la liaison:
la source
Mon exemple pour les bases de données SQLite et Oracle.
La première boucle For est destinée à la création d'objets PreparedStatement.
La deuxième boucle For est destinée à la fourniture de valeurs pour les paramètres PreparedStatement.
la source
Ma solution (JavaScript)
SearchTerms
est le tableau qui contient vos entrées / clés / champs, etc.la source