¿Cuáles son las mejores soluciones para usar una IN
cláusula SQL con instancias de java.sql.PreparedStatement
, que no es compatible con valores múltiples debido a problemas de seguridad de ataque de inyección SQL? Un ?
marcador de posición representa un valor, en lugar de una lista de valores.
Considere la siguiente instrucción SQL:
SELECT my_column FROM my_table where search_column IN (?)
El uso preparedStatement.setString( 1, "'A', 'B', 'C'" );
es esencialmente un intento que no funciona en una solución de las razones para usar ?
en primer lugar.
¿Qué soluciones hay disponibles?
Respuestas:
Un análisis de las diversas opciones disponibles, y los pros y los contras de cada uno está disponible aquí .
Las opciones sugeridas son:
SELECT my_column FROM my_table WHERE search_column = ?
, ejecútelo para cada valor y UNIONE los resultados del lado del cliente. Requiere solo una declaración preparada. Lento y dolorosoSELECT my_column FROM my_table WHERE search_column IN (?,?,?)
y ejecútalo. Requiere una declaración preparada por tamaño de lista IN. Rápido y obvio.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
y ejecútalo. [O usarUNION ALL
en lugar de esos puntos y comas. --ed] Requiere una declaración preparada por tamaño de lista IN. Estúpidamente lento, estrictamente peor queWHERE search_column IN (?,?,?)
, así que no sé por qué el blogger incluso lo sugirió.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
. Cualquier servidor decente optimizará los valores duplicados antes de ejecutar la consulta.Sin embargo, ninguna de estas opciones es súper genial.
Se han respondido preguntas duplicadas en estos lugares con alternativas igualmente sensatas, pero ninguna de ellas es súper genial:
La respuesta correcta, si está utilizando JDBC4 y un servidor compatible
x = ANY(y)
, es usarPreparedStatement.setArray
como se describe aquí:Sin embargo, no parece haber ninguna manera de hacer que
setArray
funcione con las listas IN.A veces, las instrucciones SQL se cargan en tiempo de ejecución (por ejemplo, desde un archivo de propiedades) pero requieren un número variable de parámetros. En tales casos, primero defina la consulta:
A continuación, cargue la consulta. Luego determine el número de parámetros antes de ejecutarlo. Una vez que se conoce el recuento de parámetros, ejecute:
Por ejemplo:
Para ciertas bases de datos donde no se admite pasar una matriz a través de la especificación JDBC 4, este método puede facilitar la transformación de la condición de cláusula lenta
= ?
a la más rápidaIN (?)
, que luego se puede ampliar llamando alany
método.fuente
Solución para PostgreSQL:
o
fuente
.createArrayOf()
parte, pero no estoy seguro de que la semántica estricta para los usuariosArray
esté definida por la especificación JDBC..createArrayOf
no funciona, puede hacer su propia creación manual de matriz literal comoString arrayLiteral = "{A,\"B \", C,D}"
(tenga en cuenta que "B" tiene un espacio mientras que C no) y luegostatement.setString(1,arrayLiteral)
dónde está la instrucción preparada... IN (SELECT UNNEST(?::VARCHAR[]))
o... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PD: no creo queANY
funcione con aSELECT
.)No hay manera simple AFAIK. Si el objetivo es mantener alta la relación de caché de la declaración (es decir, no crear una declaración por cada recuento de parámetros), puede hacer lo siguiente:
cree una declaración con algunos (por ejemplo, 10) parámetros:
... DONDE UN IN (?,?,?,?,?,?,?,?,?,?) ...
Vincula todos los parámetros actuales
setString (1, "foo"); setString (2, "bar");
Vincula el resto como NULL
setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)
NULL nunca coincide con nada, por lo que el generador de planes SQL lo optimiza.
La lógica es fácil de automatizar cuando pasa una Lista a una función DAO:
fuente
NULL
En la consulta coincidiría con unNULL
valor en la base de datos?NOT IN
yIN
no maneje nulos de la misma manera. Ejecute esto y vea qué sucede:select 'Matched' as did_it_match where 1 not in (5, null);
luego quite elnull
y observe la magia.a IN (1,2,3,3,3,3,3)
es lo mismo quea IN (1,2,3)
. También funciona conNOT IN
unlikea NOT IN (1,2,3,null,null,null,null)
(que siempre no devuelve filas, yaany_value != NULL
que siempre es falso).Una solución desagradable, pero ciertamente factible es usar una consulta anidada. Cree una tabla temporal MYVALUES con una columna en ella. Inserte su lista de valores en la tabla MYVALUES. Luego ejecuta
Feo, pero una alternativa viable si su lista de valores es muy grande.
Esta técnica tiene la ventaja adicional de tener planes de consulta potencialmente mejores desde el optimizador (verifique en una página los valores múltiples, el escaneo de tablas solo una vez, una vez por valor, etc.) puede ahorrar en gastos generales si su base de datos no almacena en caché las declaraciones preparadas. Sus "INSERTOS" tendrían que hacerse por lotes y la tabla MYVALUES podría necesitar ajustes para tener un bloqueo mínimo u otras protecciones de alto gasto.
fuente
Las limitaciones del operador in () son la raíz de todo mal.
Funciona para casos triviales, y puede extenderlo con "generación automática de la declaración preparada", sin embargo, siempre tiene sus límites.
El enfoque in () puede ser lo suficientemente bueno para algunos casos, pero no a prueba de cohetes :)
La solución a prueba de cohetes es pasar el número arbitrario de parámetros en una llamada separada (pasando un par de parámetros, por ejemplo), y luego tener una vista (o cualquier otra forma) para representarlos en SQL y usarlos en su criterios
Una variante de fuerza bruta está aquí http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
Sin embargo, si puede usar PL / SQL, este desastre puede volverse bastante bueno.
Luego puede pasar un número arbitrario de identificadores de cliente separados por comas en el parámetro y:
El truco aquí es:
La vista se ve así:
donde aux_in_list.getpayload se refiere a la cadena de entrada original.
Un enfoque posible sería pasar matrices pl / sql (solo compatible con Oracle), sin embargo, no puede usarlas en SQL puro, por lo tanto, siempre se necesita un paso de conversión. La conversión no se puede hacer en SQL, así que, después de todo, pasar un clob con todos los parámetros en cadena y convertirlo dentro de una vista es la solución más eficiente.
fuente
Así es como lo resolví en mi propia aplicación. Idealmente, debe usar un StringBuilder en lugar de usar + para Strings.
Usar una variable como x arriba en lugar de números concretos ayuda mucho si decides cambiar la consulta más adelante.
fuente
Nunca lo he intentado, pero ¿haría .setArray () hacer lo que estás buscando?
Actualización : evidentemente no. setArray solo parece funcionar con un java.sql.Array que proviene de una columna ARRAY que ha recuperado de una consulta anterior, o una subconsulta con una columna ARRAY.
fuente
Mi solución es:
Ahora puede usar una variable para obtener algunos valores en una tabla:
Entonces, la declaración preparada podría ser:
Saludos,
Javier Ibáñez
fuente
Supongo que podría (utilizando la manipulación básica de cadenas) generar la cadena de consulta en el
PreparedStatement
para tener un número de?
coincidencias con el número de elementos en su lista.Por supuesto, si está haciendo eso, está a solo un paso de generar un gigante encadenado
OR
en su consulta, pero sin tener el número correcto?
en la cadena de consulta, no veo cómo puede solucionar esto.fuente
Puede usar el método setArray como se menciona en este javadoc :
fuente
Puede usar
Collections.nCopies
para generar una colección de marcadores de posición y unirlos usandoString.join
:fuente
Aquí hay una solución completa en Java para crear la declaración preparada para usted:
fuente
Spring permite pasar java.util.Lists a NamedParameterJdbcTemplate , que automatiza la generación de (?,?,?, ...,?), corresponda para la cantidad de argumentos.
Para Oracle, esta publicación de blog discute el uso de oracle.sql.ARRAY (Connection.createArrayOf no funciona con Oracle). Para esto, debe modificar su declaración SQL:
La función de tabla oracle transforma la matriz pasada en una tabla como valor utilizable en la
IN
declaración.fuente
intente usar la función instr?
entonces
Es cierto que este es un truco un poco sucio, pero reduce las oportunidades para la inyección de sql. Funciona en Oracle de todos modos.
fuente
Sormula admite el operador SQL IN al permitirle proporcionar un objeto java.util.Collection como parámetro. Crea una declaración preparada con un? para cada uno de los elementos de la colección. Vea el Ejemplo 4 (SQL en el ejemplo es un comentario para aclarar lo que se crea pero no es usado por Sormula).
fuente
En lugar de usar
use la Declaración SQL como
y
o use un procedimiento almacenado, esta sería la mejor solución, ya que las instrucciones sql se compilarán y almacenarán en el servidor DataBase
fuente
Encontré una serie de limitaciones relacionadas con la declaración preparada:
Entre las soluciones propuestas, elegiría la que no disminuye el rendimiento de la consulta y hace el menor número de consultas. Este será el # 4 (agrupando pocas consultas) del enlace @Don o especificando valores NULL para '?' Innecesarios marcas propuestas por @Vladimir Dyuzhev
fuente
Acabo de elaborar una opción específica de PostgreSQL para esto. Es un truco y viene con sus propios pros, contras y limitaciones, pero parece funcionar y no se limita a un lenguaje de desarrollo específico, plataforma o controlador PG.
El truco, por supuesto, es encontrar una manera de pasar una colección arbitraria de valores de longitud como un solo parámetro, y hacer que db lo reconozca como valores múltiples. La solución que tengo trabajando es construir una cadena delimitada a partir de los valores de la colección, pasar esa cadena como un solo parámetro y usar string_to_array () con la conversión necesaria para que PostgreSQL lo use correctamente.
Entonces, si desea buscar "foo", "blah" y "abc", puede concatenarlos en una sola cadena como: 'foo, blah, abc'. Aquí está el SQL directo:
Obviamente, cambiaría la conversión explícita a lo que quisiera que fuera su matriz de valores resultante: int, text, uuid, etc. Y porque la función está tomando un solo valor de cadena (o dos, supongo, si desea personalizar el delimitador) también), puede pasarlo como parámetro en una declaración preparada:
Esto es incluso lo suficientemente flexible como para soportar cosas como comparaciones LIKE:
De nuevo, no hay duda de que es un truco, pero funciona y le permite seguir usando declaraciones preparadas previamente compiladas que toman * ejem * parámetros discretos, con los beneficios de seguridad y (tal vez) de rendimiento que lo acompañan. ¿Es aconsejable y realmente eficaz? Naturalmente, depende, ya que tiene el análisis de cadenas y posiblemente la conversión antes de que se ejecute su consulta. Si espera enviar tres, cinco, algunas docenas de valores, seguro, probablemente esté bien. Unos pocos miles? Sí, tal vez no tanto. YMMV, se aplican limitaciones y exclusiones, sin garantía expresa o implícita.
Pero funciona.
fuente
Solo para completar: siempre que el conjunto de valores no sea demasiado grande, también puede simplemente construir una declaración como
que luego puede pasar para preparar (), y luego usar setXXX () en un bucle para establecer todos los valores. Esto parece asqueroso, pero muchos sistemas comerciales "grandes" rutinariamente hacen este tipo de cosas hasta que alcanzan los límites específicos de DB, como 32 KB (creo que es) para las declaraciones en Oracle.
Por supuesto, debe asegurarse de que el conjunto nunca será excesivamente grande o hacer un error de captura en caso de que lo sea.
fuente
Siguiendo la idea de Adam. Haga que su declaración preparada seleccione my_column de my_table donde search_column en (#) Cree una Cadena x y complétela con un número de "?,?,?" dependiendo de su lista de valores Luego simplemente cambie el # en la consulta para su nueva Cadena x un poblado
fuente
Genere la cadena de consulta en PreparedStatement para que un número de? Coincida con el número de elementos en su lista. Aquí hay un ejemplo:
fuente
StringBuilder
. Pero no en la forma en que piensas. Al descompilargenerateQsForIn
, puede ver que por iteración de bucle se asignan dos nuevosStringBuilder
ytoString
se llama a cada uno. LaStringBuilder
optimización solo captura cosas como,"x" + i+ "y" + j
pero no se extiende más allá de una expresión.ps.setObject(1,items)
lugar de iterar sobre la lista y luego configurar elparamteres
?Existen diferentes enfoques alternativos que podemos usar para la cláusula IN en PreparedStatement.
Use NULL en consultas PreparedStatement: rendimiento óptimo, funciona muy bien cuando conoce el límite de los argumentos de la cláusula IN. Si no hay límite, puede ejecutar consultas en lote. El fragmento de código de muestra es;
Puede consultar más detalles sobre estos enfoques alternativos aquí .
fuente
Para algunas situaciones, regexp podría ayudar. Aquí hay un ejemplo que he comprobado en Oracle, y funciona.
Pero hay una serie de inconvenientes:
fuente
Después de examinar varias soluciones en diferentes foros y no encontrar una buena solución, creo que el siguiente truco que se me ocurrió es el más fácil de seguir y codificar:
Ejemplo: suponga que tiene que pasar varios parámetros en la cláusula 'IN'. Simplemente coloque una Cadena ficticia dentro de la cláusula 'IN', diga, "PARAM" denote la lista de parámetros que vendrán en lugar de esta Cadena ficticia.
Puede recopilar todos los parámetros en una sola variable de cadena en su código Java. Esto puede hacerse de la siguiente manera:
Puede agregar todos sus parámetros separados por comas en una sola variable de cadena, 'param1', en nuestro caso.
Después de recopilar todos los parámetros en una sola Cadena, puede reemplazar el texto ficticio en su consulta, es decir, "PARAM" en este caso, con el parámetro Cadena, es decir, param1. Aquí está lo que tú necesitas hacer:
Ahora puede ejecutar su consulta utilizando el método executeQuery (). Solo asegúrese de no tener la palabra "PARAM" en su consulta en ninguna parte. Puede usar una combinación de caracteres especiales y alfabetos en lugar de la palabra "PARAM" para asegurarse de que no haya posibilidad de que dicha palabra aparezca en la consulta. Espero que hayas encontrado la solución.
Nota: Aunque esta no es una consulta preparada, hace el trabajo que quería que hiciera mi código.
fuente
Solo por completo y porque no vi a nadie más sugerirlo:
Antes de implementar cualquiera de las complicadas sugerencias anteriores, considere si la inyección SQL es realmente un problema en su escenario.
En muchos casos, el valor proporcionado a IN (...) es una lista de identificadores que se han generado de manera que pueda estar seguro de que no es posible la inyección ... (por ejemplo, los resultados de una selección previa de some_id de some_table donde alguna_condición.)
Si ese es el caso, puede concatenar este valor y no usar los servicios o la declaración preparada para él o usarlos para otros parámetros de esta consulta.
fuente
PreparedStatement no proporciona ninguna buena manera de lidiar con la cláusula SQL IN. De acuerdo con http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "No puede sustituir cosas que están destinadas a formar parte de la instrucción SQL. Esto es necesario porque si el SQL mismo puede cambiar, el el controlador no puede precompilar la declaración. También tiene el agradable efecto secundario de prevenir ataques de inyección SQL ". Terminé usando el siguiente enfoque:
fuente
SetArray es la mejor solución, pero no está disponible para muchos controladores antiguos. La siguiente solución alternativa se puede utilizar en java8
Esta solución es mejor que otras soluciones feas mientras que las cadenas de consulta se construyen mediante iteraciones manuales.
fuente
Esto funcionó para mí (psuedocode):
especifique vinculante:
fuente
Mi ejemplo para las bases de datos SQLite y Oracle.
El primer bucle For es para crear un objeto PreparedStatement.
El segundo bucle For es para suministrar valores para parámetros de declaración preparada.
fuente
Mi solución alternativa (JavaScript)
SearchTerms
es la matriz que contiene su entrada / claves / campos, etc.fuente