Каковы наилучшие обходные пути для использования предложения SQL IN с экземплярами java.sql.PreparedStatement, которое не поддерживается для нескольких значений из-за проблем с безопасностью атаки SQL-инъекции: один заполнитель ? представляет собой одно значение, а не список значений.
Рассмотрим следующий оператор SQL:
SELECT my_column FROM my_table where search_column IN (?)
Использование preparedStatement.setString( 1, "'A', 'B', 'C'" ); - это, по сути, неработающая попытка обойти причины использования ? в первую очередь.
Какие обходные пути доступны?
Одним из преимуществ подготовленных операторов является то, что sohuld может быть скомпилирован один раз для повышения эффективности. Делая предложение in динамическим, это эффективно отменяет подготовленный оператор.
Фактически, это работает для MySQL (используя setObject для установки массива String в качестве значения параметра). Какую БД вы используете?
Вот связанный с этим вопрос: stackoverflow.com/q/6956025/521799
@Frans у меня не работает. Он выполняет запрос, но всегда безрезультатно.
Конкретный вопрос по MSSQL от основателей SO: stackoverflow.com/questions/337704/…




Я никогда не пробовал, но будет ли .setArray () делать то, что вы ищете?
Обновлять: Очевидно, нет. setArray, похоже, работает только с java.sql.Array, который поступает из столбца ARRAY, который вы получили из предыдущего запроса, или подзапроса со столбцом ARRAY.
Не работает со всеми базами данных, но это «правильный» подход.
Вы имеете в виду всех драйверов. Некоторые драйверы имеют проприетарные эквиваленты стандарта этого года (прошлого века?). Другой способ - вставить пакет значений во временную таблицу, но не все базы данных поддерживают это ...
Я полагаю, вы могли бы (используя базовые манипуляции со строками) сгенерировать строку запроса в PreparedStatement, чтобы количество ? соответствовало количеству элементов в вашем списке.
Конечно, если вы делаете это, вы всего в шаге от создания гигантского сцепленного OR в своем запросе, но без правильного числа ? в строке запроса, я не вижу, как еще вы можете обойти это .
На самом деле это не решение для меня, так как я хочу отправить другое количество? каждый раз, когда я вызываю пс. Но не думайте, что я не подумал об этом. :П
Еще один прием: вы можете использовать большое количество заполнителей параметров - столько, сколько будет самый длинный список значений, который у вас будет - и если ваш список значений короче, вы можете повторять значения: ... WHERE searchfield IN (? ,?,?,?,?,?,?,?), а затем укажите значения: A, B, C, D, A, B, C, D
Но в целом я предпочитаю решение Адама: динамически генерировать SQL и объединять? заполнители, соответствующие количеству передаваемых значений.
Билл, это решение работает, если я не хочу повторно использовать PreparedStatement. Другое решение - выполнить вызов одного параметра несколько раз и накапливать результаты на стороне клиента. Вероятно, было бы более эффективно создать / выполнить новый оператор с произвольным числом? хотя каждый раз.
попробовать использовать функцию instr?
select my_column from my_table where instr(?, ','||search_column||',') > 0
потом
ps.setString(1, ",A,B,C,");
По общему признанию, это немного грязный прием, но он снижает возможности внедрения sql. В любом случае работает в Oracle.
О, и я знаю, что он не будет использовать индексы
это не сработает для некоторых строк, например, если строка содержит ','.
Просто для полноты: пока набор значений не слишком велик, вы также мог просто строите строку для такого оператора, как
... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?
который затем можно передать в prepare (), а затем использовать setXXX () в цикле для установки всех значений. Это выглядит неприятно, но многие «большие» коммерческие системы обычно делают такие вещи, пока не достигают специфичных для БД ограничений, таких как 32 КБ (я думаю, что это так) для операторов в Oracle.
Конечно, вам нужно убедиться, что набор никогда не будет чрезмерно большим, и не выполнять перехват ошибок в том случае, если это так.
Да, ты прав. В этом случае моей целью было повторно использовать PreparedStatement с разным количеством элементов каждый раз.
Использование «ИЛИ» запутает намерение. Придерживайтесь «IN», так как его легче читать и смысл более ясен. Единственная причина для переключения - если планы запроса были другими.
Следуя идее Адама. Сделайте свой подготовленный оператор вроде select my_column из my_table, где search_column в (#) Создайте строку x и заполните ее числом "?,?,?" в зависимости от вашего списка ценностей Затем просто измените # в запросе для вашей новой строки x и заполните
Неприятный, но вполне выполнимый обходной путь - использовать вложенный запрос. Создайте временную таблицу MYVALUES со столбцом в ней. Вставьте свой список значений в таблицу MYVALUES. Затем выполните
select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )
Уродливая, но жизнеспособная альтернатива, если у вас очень большой список ценностей.
Этот метод имеет дополнительное преимущество в виде потенциально лучших планов запросов от оптимизатора (проверяйте страницу на наличие нескольких значений, сканирование таблиц только один раз вместо одного для каждого значения и т. д.) Может сэкономить накладные расходы, если ваша база данных не кэширует подготовленные операторы. Ваши «INSERTS» должны быть выполнены в пакетном режиме, а таблица MYVALUES, возможно, потребуется настроить, чтобы иметь минимальную блокировку или другие средства защиты с высокими накладными расходами.
Какие преимущества это будет по сравнению с запросом my_table по одному значению за раз?
Оптимизатор запросов может снизить нагрузку ввода-вывода, извлекая все возможные совпадения с загруженной страницы. Сканирование таблиц или индекса может выполняться один раз, а не один раз для каждого значения. Накладные расходы на вставку значений могут быть уменьшены с помощью пакетных операций и могут быть меньше, чем несколько запросов.
выглядит неплохо, но могут быть проблемы с параллелизмом. содержит ли спецификация jdbc способ создания временной анонимной таблицы в памяти? или что-то в этом роде, если возможно, не конкретное jdbc-vendor?
Не простой способ AFAIK. Если цель состоит в том, чтобы поддерживать высокий коэффициент кеширования операторов (т. Е. Не создавать оператор для каждого количества параметров), вы можете сделать следующее:
создать оператор с несколькими (например, 10) параметрами:
... ГДЕ А В (?,?,?,?,?,?,?,?,?,?) ...
Привязать все актуальные параметры
setString (1, «фу»); setString (2, «бар»);
Свяжите остальные как NULL
setNull (3, Типы.VARCHAR) ... setNull (10, Типы.VARCHAR)
NULL никогда ничего не соответствует, поэтому он оптимизируется построителем планов SQL.
Логику легко автоматизировать, если передать список в функцию DAO:
while( i < param.size() ) {
ps.setString(i+1,param.get(i));
i++;
}
while( i < MAX_PARAMS ) {
ps.setNull(i+1,Types.VARCHAR);
i++;
}
«NULL никогда ничего не соответствует» - Соответствует ли NULL в запросе значению NULL в базе данных?
@CraigMcQueen Нет, это не так. Null даже не соответствует нулю, согласно стандарту ANSI.
Вы можете сопоставить NULL с помощью ключевого слова IS NULL. Хороший способ обнаружить строки, которых нет в объединенной таблице, - использовать LEFT JOIN вместе с IS NULL. 'SELECT a.URL, b.URL FROM TABLE_A a LEFT JOIN TABLE_B b ON a_A.URL = b_B.URL WHERE b.URL IS NULL' Это покажет все строки в таблице A, которые не соответствуют в таблице B.
Но будьте осторожны с этим. NOT IN и IN не обрабатывают значения NULL одинаково. Запустите это и посмотрите, что произойдет: select 'Matched' as did_it_match where 1 not in (5, null); Затем удалите null и наблюдайте за волшебством.
Или вы можете установить для всех дополнительных параметров значение любого предыдущего параметра. Любой достойный движок БД отфильтрует их. Итак, a IN (1,2,3,3,3,3,3) - это то же самое, что a IN (1,2,3). Он также работает с NOT IN, в отличие от a NOT IN (1,2,3,null,null,null,null) (который всегда не возвращает строк, поскольку any_value != NULL всегда ложен).
Доступен анализ различных доступных опций, а также плюсы и минусы каждого из них здесь.
Предлагаемые варианты:
SELECT my_column FROM my_table WHERE search_column = ?, выполните его для каждого значения и СОЕДИНИТЕ результаты на стороне клиента. Требуется только один подготовленный оператор. Медленно и болезненно.SELECT my_column FROM my_table WHERE search_column IN (?,?,?) и выполните его. Требуется одна подготовленная инструкция для каждого размера IN-list. Быстро и очевидно.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... и выполните его. [Или используйте UNION ALL вместо точек с запятой. --ed] Требуется один подготовленный оператор для каждого списка размера IN. Глупо медленно, строго хуже, чем WHERE search_column IN (?,?,?), поэтому я не знаю, почему блогер даже предложил это.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6). Любой достойный сервер оптимизирует повторяющиеся значения перед выполнением запроса.Однако ни один из этих вариантов не является отличным.
В этих местах были даны ответы на повторяющиеся вопросы с одинаково разумными альтернативами, но все же ни один из них не был супер-отличным:
Правильный ответ, если вы используете JDBC4 и сервер, поддерживающий x = ANY(y), - использовать PreparedStatement.setArray, как описано здесь:
Однако, похоже, нет никакого способа заставить setArray работать со списками IN.
Иногда операторы SQL загружаются во время выполнения (например, из файла свойств), но требуют переменного количества параметров. В таких случаях сначала определите запрос:
query=SELECT * FROM table t WHERE t.column IN (?)
Затем загрузите запрос. Затем определите количество параметров перед запуском. Как только количество параметров известно, запустите:
sql = any( sql, count );
Например:
/**
* 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(
String.join(", ", Collections.nCopies(possibleValue.size(), "?")));
// 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;
}
Для некоторых баз данных, где передача массива через спецификацию JDBC 4 не поддерживается, этот метод может облегчить преобразование медленного = ? в более быстрое условие предложения IN (?), которое затем можно расширить, вызвав метод any.
Создайте строку запроса в PreparedStatement, чтобы число? Соответствовало количеству элементов в вашем списке. Вот пример:
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;
}
Больше нет необходимости использовать StringBuilder. Компилятор все равно преобразует знаки + в StringBuilder.append (), поэтому производительность не снижается. Попробуйте сами :)
@ neu242: Да, компилятор использует StringBuilder. Но не так, как вы думаете. Декомпилируя generateQsForIn, вы можете увидеть, что для каждой итерации цикла выделяется два нового StringBuilder и для каждого вызывается toString. Оптимизация StringBuilder улавливает только такие вещи, как "x" + i+ "y" + j, но не выходит за рамки одного выражения.
@ neu242 Разве нельзя использовать ps.setObject(1,items) вместо перебора списка и последующей установки paramteres?
Мое решение:
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;
/
Теперь вы можете использовать одну переменную для получения некоторых значений в таблице:
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
Итак, подготовленный оператор может быть:
"select * from TABLE where COL in (select * from table(split(?)))"
С уважением,
Хавьер Ибанез
Это PL / SQL, да. В других базах данных работать не будет. Обратите внимание, что эта реализация имеет ограничение входных параметров (общая длина ограничена 32 КБ символов), а также ограничение производительности, поскольку вызов конвейерной функции выполняет переключение контекста между PL / SQL и SQL-механизмами Oracle.
Решение для 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));
try (ResultSet rs = statement.executeQuery()) {
while(rs.next()) {
// do some...
}
}
или же
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));
try (ResultSet rs = statement.executeQuery()) {
while(rs.next()) {
// do some...
}
}
выглядит хорошо. какую часть этого кода указывает PostgreSQL? "где столбец поиска = ЛЮБОЙ (?)"? или connection.createArrayOf? или что-то другое?
Я думаю, что он более специфичен для JDBC4, чем для PostgreSQL, из-за части .createArrayOf(), но я не уверен, что строгая семантика для пользовательских Array определяется спецификацией JDBC.
Если .createArrayOf не работает, вы можете вручную создать литерал массива, например String arrayLiteral = "{A,\"B \", C,D}"(обратите внимание, что в "B" есть пробел, а в C нет), а затем statement.setString(1,arrayLiteral), где подготовленным оператором является ... IN (SELECT UNNEST(?::VARCHAR[])) или ... IN (SELECT UNNEST(CAST(? AS VARCHAR[]))). (PS: я не думаю, что ANY работает с SELECT.)
Отличное решение! Действительно спас мне день. Для целочисленного массива я использовал "int" в первом параметре createArrayOf (), и это выглядит хорошо. Однако, если судить по документации, этот первый параметр специфичен для БД.
Это кажется самым чистым решением. Если кто-то ищет специфический синтаксис HSQLDB: мне удалось заставить его работать с IN (UNNEST (?))
Жаль, что функция JDBC не имеет широкой поддержки в драйверах БД. Ни один из драйверов для MSSQL, которые я пробовал, не реализовал. Тем не менее, мне нравится, что если я когда-нибудь напишу специфичный для PGSQL код, это будет выглядеть аккуратно.
Когда я реализую это, я получаю: ОШИБКА: аргумент IN не должен возвращать набор
@Ivella Если я понимаю решение, то, что специфично для PostgreSQL, является возможность использования параметра массива в качестве списка значений. Обычно параметры массива используются для типов массивов sql, как показано здесь docs.oracle.com/javase/tutorial/jdbc/basics/array.html
Я использовал jdbcTemplate с этим ЛЮБЫМ форматом. Единственное, что мне пришлось настроить abit, чтобы заставить его работать, - это преобразовать список / массив значений, входящих в качестве аргумента, в строку с отдельными значениями, разделенными запятыми. Мне также нужно было поставить {перед строкой и закончить ее}. Затем в ЛЮБОЙ (?) Я помещаю ЛЮБОЙ (: theCommaSeparatedValuesString :: long []). Может быть, был бы даже более простой и изящный способ сделать это, но я остановился на нем, так как он работал. Я создал метод удаления, используя это.
вместо использования
SELECT my_column FROM my_table where search_column IN (?)
используйте оператор Sql как
select id, name from users where id in (?, ?, ?)
и
preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');
или используйте хранимую процедуру, это было бы лучшим решением, поскольку операторы sql будут скомпилированы и сохранены на сервере базы данных
Я столкнулся с рядом ограничений, связанных с подготовленным оператором:
Среди предложенных решений я бы выбрал то, которое не снижает производительность запросов и делает меньше запросов. Это будет номер 4 (пакетирование нескольких запросов) из ссылки @Don или указание значений NULL для ненужных символов '?' отметки по предложению @Vladimir Dyuzhev
Существуют разные альтернативные подходы, которые мы можем использовать для предложения IN в PreparedStatement.
Используйте NULL в запросах PreparedStatement - Оптимальная производительность, отлично работает, когда вы знаете предел аргументов предложения IN. Если ограничения нет, то вы можете выполнять запросы в пакетном режиме. Фрагмент кода примера:
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);
}
Вы можете проверить более подробную информацию об этих альтернативных подходах здесь.
«Создание динамического запроса для PreparedStatement - хорошая производительность, но без кеширования, и PreparedStatement перекомпилируется каждый раз». кэширование и предотвращение перекомпиляции - вот что делает подготовленный оператор хорошо работающим. Поэтому я не согласен с вашим утверждением. Однако это предотвратит внедрение SQL, поскольку вы ограничиваете конкатенированный / динамический ввод запятой.
Я согласен с вами, однако «Хорошая производительность» здесь предназначена для этого конкретного сценария. Он лучше, чем подход 1, однако подход 2 является самым быстрым.
В некоторых ситуациях может помочь регулярное выражение. Вот пример, который я проверял на Oracle, и он работает.
select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')
Но у него есть ряд недостатков:
Изучив различные решения на разных форумах и не найдя хорошего решения, я считаю, что приведенный ниже прием, который я придумал, является самым простым для выполнения и кодирования:
Пример: Предположим, у вас есть несколько параметров для передачи в предложении IN. Просто поместите фиктивную строку внутри предложения 'IN', скажем, «PARAM» действительно обозначает список параметров, которые будут приходить на место этой фиктивной строки.
select * from TABLE_A where ATTR IN (PARAM);
Вы можете собрать все параметры в одну строковую переменную в вашем Java-коде. Это можно сделать следующим образом:
String param1 = "X";
String param2 = "Y";
String param1 = param1.append(",").append(param2);
Вы можете добавить все свои параметры, разделенные запятыми, в одну строковую переменную, 'param1', в нашем случае.
После объединения всех параметров в одну строку вы можете просто заменить фиктивный текст в своем запросе, то есть "PARAM" в данном случае, параметром String, то есть param1. Вот что вам нужно сделать:
String query = query.replaceFirst("PARAM",param1); where we have the value of query as
query = "select * from TABLE_A where ATTR IN (PARAM)";
Теперь вы можете выполнить свой запрос, используя метод executeQuery (). Просто убедитесь, что в вашем запросе нет слова «PARAM». Вы можете использовать комбинацию специальных символов и алфавитов вместо слова «ПАРАМЕТР», чтобы исключить возможность появления такого слова в запросе. Надеюсь, у вас есть решение.
Примечание. Хотя это не подготовленный запрос, он выполняет ту работу, которую я хотел, чтобы мой код выполнял.
Просто для полноты и потому, что я не видел, чтобы кто-то еще предлагал это:
Перед реализацией любого из приведенных выше сложных предложений подумайте, действительно ли SQL-инъекция является проблемой в вашем сценарии.
Во многих случаях значение, предоставленное IN (...), представляет собой список идентификаторов, которые были сгенерированы таким образом, чтобы вы могли быть уверены, что никакая инъекция невозможна ... (например, результаты предыдущего выбора some_id из some_table, где некоторое_условие.)
В этом случае вы можете просто объединить это значение и не использовать для него службы или подготовленный оператор или использовать их для других параметров этого запроса.
query = "select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
Вот полное решение на Java для создания подготовленного оператора за вас:
/*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;
}
}
Spring позволяет использовать передача java.util.Lists в NamedParameterJdbcTemplate, который автоматизирует создание (?,?,?, ...,?) В зависимости от количества аргументов.
Для Oracle это сообщение в блоге обсуждает использование oracle.sql.ARRAY (Connection.createArrayOf не работает с Oracle). Для этого вам нужно изменить свой оператор SQL:
SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))
функция таблицы оракула преобразует переданный массив в табличное значение, используемое в операторе IN.
Он работает для тривиальных случаев, и вы можете расширить его с помощью «автоматической генерации подготовленного оператора», однако у него всегда есть свои ограничения.
Подход in () может быть достаточно хорош для некоторых случаев, но не для ракетной защиты :)
Противоракетное решение состоит в том, чтобы передать произвольное количество параметров в отдельном вызове (например, путем передачи большого количества параметров), а затем иметь представление (или любой другой способ), чтобы представить их в SQL и использовать в вашем where критерии.
Вариант грубой силы здесь http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
Однако, если вы можете использовать PL / SQL, этот беспорядок может стать довольно изящным.
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;
Затем вы можете передать в параметре произвольное количество идентификаторов клиентов, разделенных запятыми, и:
Уловка вот в чем:
Вид выглядит так:
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
где aux_in_list.getpayload относится к исходной входной строке.
Возможный подход - передать массивы pl / sql (поддерживаются только Oracle), однако вы не можете использовать их в чистом SQL, поэтому всегда требуется этап преобразования. Преобразование не может быть выполнено в SQL, поэтому, в конце концов, передача clob со всеми параметрами в строке и преобразование его в представление является наиболее эффективным решением.
Вот как я решил это в собственном приложении. В идеале вы должны использовать StringBuilder вместо использования + для строк.
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();
}
Использование переменной типа x вместо конкретных чисел очень помогает, если вы решите изменить запрос позже.
Вы можете использовать метод setArray, как указано в этот 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();
это поддерживается не всеми драйверами, если функция не поддерживается, вы получите SQLFeatureNotSupportedException
К сожалению, мой драйвер не поддерживает это
Мой способ обхода (JavaScript)
var s1 = " SELECT "
+ "FROM table t "
+ " where t.field in ";
var s3 = '(';
for(var i =0;i<searchTerms.length;i++)
{
if (i+1 == searchTerms.length)
{
s3 = s3+'?)';
}
else
{
s3 = s3+'?, ' ;
}
}
var query = s1+s3;
var pstmt = connection.prepareStatement(query);
for(var i =0;i<searchTerms.length;i++)
{
pstmt.setString(i+1, searchTerms[i]);
}
SearchTerms - это массив, который содержит ваши входные данные / ключи / поля и т. д.
PreparedStatement не предоставляет никакого хорошего способа справиться с предложением SQL IN. Per http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Вы не можете заменять вещи, которые должны стать частью инструкции SQL. Это необходимо, потому что, если сам SQL может измениться, драйвер не может предварительно скомпилировать инструкцию. Это также имеет приятный побочный эффект предотвращения SQL инъекционные атаки ". В итоге я использовал следующий подход:
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);
SetArray - лучшее решение, но оно недоступно для многих старых драйверов. В 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);
}
Это решение лучше, чем другие уродливые решения цикла while, в которых строка запроса создается вручную.
Вы можете использовать Collections.nCopies для создания коллекции заполнителей и присоединить их с помощью 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
*/
}
Кажется, пока что это лучшее решение при использовании Oracle JDBC ...
Я только что разработал для этого вариант, специфичный для PostgreSQL. Это что-то вроде взлома и имеет свои плюсы, минусы и ограничения, но, похоже, он работает и не ограничивается конкретным языком разработки, платформой или драйвером PG.
Уловка, конечно же, заключается в том, чтобы найти способ передать набор значений произвольной длины в качестве одного параметра и заставить базу данных распознать его как несколько значений. Решение, над которым я работаю, состоит в том, чтобы создать строку с разделителями из значений в коллекции, передать эту строку как один параметр и использовать string_to_array () с необходимым преобразованием для PostgreSQL, чтобы правильно ее использовать.
Поэтому, если вы хотите искать «foo», «blah» и «abc», вы можете объединить их в одну строку как: 'foo, blah, abc'. Вот простой SQL:
select column from table
where search_column = any (string_to_array('foo,blah,abc', ',')::text[]);
Вы, очевидно, измените явное приведение на то, что вы хотите, чтобы ваш результирующий массив значений был - int, text, uuid и т.д. И поскольку функция принимает одно строковое значение (или два, я полагаю, если вы хотите настроить разделитель также), вы можете передать его как параметр в подготовленном операторе:
select column from table
where search_column = any (string_to_array(, ',')::text[]);
Это даже достаточно гибко, чтобы поддерживать такие вещи, как сравнения LIKE:
select column from table
where search_column like any (string_to_array('foo%,blah%,abc%', ',')::text[]);
Опять же, нет никаких сомнений в том, что это взлом, но он работает и позволяет вам по-прежнему использовать предварительно скомпилированные подготовленные операторы, которые принимают дискретные параметры * кхм *, с соответствующими преимуществами безопасности и (возможно) производительности. Является ли это целесообразным и действительно эффективным? Естественно, это зависит от того, у вас есть синтаксический анализ строк и, возможно, приведение типов, прежде чем ваш запрос даже будет запущен. Если вы ожидаете отправить три, пять, несколько десятков значений, конечно, это нормально. Несколько тысяч? Да, может быть, не так уж и много. YMMV, применяются ограничения и исключения, никаких гарантий, явных или подразумеваемых.
Но это работает.
Это сработало для меня (псевдокод):
public class SqlHelper
{
public static final ArrayList<String>platformList = new ArrayList<>(Arrays.asList("iOS","Android","Windows","Mac"));
public static final String testQuery = "select * from devices where platform_nm in (:PLATFORM_NAME)";
}
указать привязку:
public class Test extends NamedParameterJdbcDaoSupport
public List<SampleModelClass> runQuery()
{
//define rowMapper to insert in object of SampleClass
final Map<String,Object> map = new HashMap<>();
map.put("PLATFORM_LIST",DeviceDataSyncQueryConstants.platformList);
return getNamedParameterJdbcTemplate().query(SqlHelper.testQuery, map, rowMapper)
}
Оскар, я думаю, что динамическая генерация (?,?, ....) - это простейший обходной путь, если вам нужно предложение IN, но я оставил это для отдельных вызовов, поскольку в моем конкретном случае производительность была достаточной.