Подготовленные альтернативы предложения AP?

Каковы наилучшие обходные пути для использования предложения SQL IN с экземплярами java.sql.PreparedStatement, который не поддерживается для нескольких значений из-за проблем безопасности SQL-атаки: один ? placeholder представляет одно значение, а не список значений.

Рассмотрим следующий оператор SQL:

SELECT my_column FROM my_table where search_column IN (?)

Использование preparedStatement.setString( 1, "'A', 'B', 'C'" ); является, по сути, нерабочей попыткой обходного пути причин использования ?.

Какие способы обхода доступны?

Ответы

Ответ 1

Анализ различных доступных вариантов, а также плюсы и минусы каждого доступны здесь.

Предлагаемые варианты:

  • Подготовить SELECT my_column FROM my_table WHERE search_column =? , выполнить его для каждого значения и объединить результаты на стороне клиента. Требуется только одно подготовленное выражение. Медленно и больно.
  • Подготовьте SELECT my_column FROM my_table WHERE search_column IN (?,?,?) И выполните его. Требуется одно подготовленное утверждение на размер списка. Быстро и очевидно.
  • Подготовить SELECT my_column FROM my_table WHERE search_column =?; SELECT my_column FROM my_table WHERE search_column =? ;... SELECT my_column FROM my_table WHERE search_column =?; SELECT my_column FROM my_table WHERE search_column =? ;... SELECT my_column FROM my_table WHERE search_column =?; SELECT my_column FROM my_table WHERE search_column =? ;... и выполнить его. [Или используйте UNION ALL вместо этих точек с запятой. --ed] Требуется одно подготовленное утверждение на размер списка. Глупо медленно, строго хуже, чем WHERE search_column IN (?,?,?), Поэтому я не знаю, почему блоггер даже предложил это.
  • Используйте хранимую процедуру для построения набора результатов.
  • Подготовить N запросов разного размера в списке; скажем, с 2, 10 и 50 значениями. Чтобы найти IN-список с 6 различными значениями, заполните запрос size-10 так, чтобы он выглядел как 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 похоже, нет никакого способа заставить 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(
            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;
}

Для некоторых баз данных, где передача массива через спецификацию JDBC 4 не поддерживается, этот метод может облегчить преобразование slow =? в более быстрое условие условия IN (?), которое затем можно развернуть, вызвав метод any.

Ответ 2

Решение для 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();
}

или

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

Ответ 3

Нет простого способа AFAIK. Если цель состоит в том, чтобы поддерживать высокий коэффициент кэша операторов (т.е. Не создавать оператор на каждый счетчик параметров), вы можете сделать следующее:

  • создайте инструкцию с несколькими (например, 10) параметрами:

    ... ГДЕ A IN (?,?,?,?,?,?,?,?,?,?)...

  • Привязать все управляющие параметры

    SetString (1, "Foo" ); SetString (2, "бар" );

  • Привяжите остальные как NULL

    SetNull (3, Types.VARCHAR) ... SetNull (10, Types.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++;
}

Ответ 4

Неприятная работа, но, безусловно, возможно использовать вложенный запрос. Создайте временную таблицу MYVALUES со столбцом в ней. Вставьте свой список значений в таблицу MYVALUES. Затем выполните

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

Уродливая, но жизнеспособная альтернатива, если ваш список значений очень велик.

Этот метод обладает дополнительным преимуществом потенциально лучших планов запросов от оптимизатора (проверьте страницу на несколько значений, таблицы могут быть указаны только один раз вместо одного значения и т.д.) могут сэкономить накладные расходы, если ваша база данных не кэширует подготовленные операторы. Ваши "INSERTS" должны выполняться в пакетном режиме, и таблица MYVALUES может потребоваться изменить, чтобы иметь минимальную блокировку или другие высоконадежные защиты.

Ответ 5

Ограничения оператора in() являются корнем всего зла.

Он работает для тривиальных случаев, и вы можете расширить его с помощью "автоматической генерации подготовленного оператора", однако он всегда имеет свои пределы.

  • если вы создаете оператор с переменным числом параметров, который будет создавать служебные данные SQL для каждого вызова
  • на многих платформах число параметров оператора in() ограничено
  • на всех платформах общий размер текста SQL ограничен, что делает невозможным отправку 2000 заполнителей для параметров in
  • отправка переменных связывания 1000-10k невозможна, так как драйвер JDBC имеет свои ограничения

Подход in() может быть достаточно хорош для некоторых случаев, но не для проверки ракеты:)

Ракетно-защищенное решение состоит в том, чтобы передать произвольное количество параметров в отдельный вызов (например, путем передачи клока параметров), а затем иметь представление (или любой другой способ) для представления их в SQL и использовать в ваших критериях.

Вариант грубой силы здесь 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;

Затем вы можете передать произвольное количество идентификаторов клиентов, разделенных запятыми, в параметре и:

  • не получит задержку синтаксического анализа, так как SQL для выбора является стабильным
  • отсутствие сложных конвейерных функций - это всего лишь один запрос
  • SQL использует простое соединение, а не оператор IN, который довольно быстро
  • В конце концов, это хорошее эмпирическое правило не попадать в базу данных с любым простым выбором или DML, поскольку это Oracle, который предлагает lightyears больше, чем MySQL или аналогичные простые механизмы базы данных. PL/SQL позволяет скрыть модель хранилища от вашей модели домена приложения эффективным способом.

Трюк здесь:

  • нам нужен вызов, который принимает длинную строку и хранит где-нибудь, где доступ к ней может иметь сеанс db (например, простая переменная пакета или dbms_session.set_context)
  • тогда нам нужно представление, которое может анализировать это на строки
  • а затем у вас есть представление, которое содержит идентификаторы, которые вы запрашиваете, поэтому все, что вам нужно, - это простое соединение с запрошенной таблицей.

Вид выглядит так:

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 со всеми параметрами в строке и преобразование его в представление является наиболее эффективным решением.

Ответ 6

Я никогда не пробовал, но будет ли .setArray() делать то, что вы ищете?

Обновить. Очевидно, нет. Кажется, что setArray работает с java.sql.Array, который исходит из столбца ARRAY, который вы извлекли из предыдущего запроса, или подзапроса с столбцом ARRAY.

Ответ 7

Мое обходное решение:

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(?)))"

Привет,

Хавьер Ибанес

Ответ 8

Вот как я решил это в своем приложении. В идеале вы должны использовать 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 выше, а не конкретных чисел, помогает, если вы решите изменить запрос позже.

Ответ 9

Я полагаю, вы могли бы (используя базовые манипуляции с строкой) генерировать строку запроса в PreparedStatement, чтобы число ? соответствовало количеству элементов в вашем списке.

Конечно, если вы делаете это, вы всего лишь шаг от создания гигантского прикованного OR в своем запросе, но без правильного количества ? в строке запроса я не вижу как еще вы можете обойти это.

Ответ 10

Вы можете использовать метод 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();

Ответ 11

попробуйте использовать функцию instr?

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

затем

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

По общему признанию, это немного грязный взлом, но он уменьшает возможности для SQL-инъекций. Все равно работает в oracle.

Ответ 12

Sormula поддерживает оператор SQL IN, позволяя вам предоставить объект java.util.Collection в качестве параметра. Он создает подготовленное выражение с? для каждого из элементов коллекции. См. Пример 4 (SQL в примере - комментарий, чтобы уточнить, что создано, но не используется Sormula).

Ответ 13

вместо

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 будут скомпилированы и сохранены на сервере DataBase

Ответ 14

Я столкнулся с рядом ограничений, связанных с подготовленным выражением:

  • Подготовленные операторы кэшируются только внутри одного сеанса (Postgres), поэтому он будет работать только с пулом соединений
  • Множество разных подготовленных операторов, предложенных @BalusC, может привести к переполнению кеша, а ранее кэшированные операторы будут удалены.
  • Запрос должен быть оптимизирован и использовать индексы. Звучит очевидно, однако, например, выражение ANY (ARRAY...), предложенное @Boris в одном из лучших ответов, не может использовать индексы, и запрос будет медленным, несмотря на кэширование
  • Подготовленный оператор также кэширует план запроса, а фактические значения любых параметров, указанных в инструкции, недоступны.

Среди предлагаемых решений я бы выбрал тот, который не уменьшает производительность запроса и уменьшает количество запросов. Это будет # 4 (партия нескольких запросов) из ссылки @Don или указание значений NULL для ненужных '?' знаки, предложенные Владимиром Дюжевым

Ответ 15

Здесь полное решение в 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;

    }

}

Ответ 16

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(?))

Функция oracle table преобразует переданный массив в таблицу как значение, которое можно использовать в инструкции IN.

Ответ 17

Просто для полноты: пока набор значений не слишком велик, вы также можете просто построить конструкцию оператора типа

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

который затем можно передать в prepare(), а затем использовать setXXX() в цикле для установки всех значений. Это выглядит yucky, но многие "большие" коммерческие системы обычно делают такие вещи до тех пор, пока они не попадут в ограничения, специфичные для БД, например 32 КБ (я думаю, это так) для операторов в Oracle.

Конечно, вам нужно убедиться, что набор никогда не будет необоснованно большим, или уловить ошибки в том случае, если это так.

Ответ 18

Следуя идее Адама. Сделайте свой подготовленный оператор вроде select my_column из my_table, где search_column in (#) Создайте строку x и заполните ее числом "?,?,?". в зависимости от вашего списка значений Затем просто измените # в запросе для вашей новой строки String x для заполнения

Ответ 19

Сгенерировать строку запроса в 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;
}

Ответ 20

Существуют различные альтернативные подходы, которые мы можем использовать для предложения IN в PreparedStatement.

  • Использование одиночных запросов - самая медленная производительность и ресурсоемкость
  • Использование StoredProcedure - самый быстрый, но специфичный для базы данных
  • Создание динамического запроса для PreparedStatement - хорошая производительность, но не получает преимущества кэширования, и 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);
        }
    

Подробнее об этих альтернативных подходах можно узнать здесь.

Ответ 21

В некоторых ситуациях regexp может помочь. Вот пример, который я проверил в Oracle, и он работает.

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

Но есть ряд недостатков:

  • Любой используемый столбец должен быть преобразован в varchar/ char, по крайней мере неявно.
  • Нужно быть осторожным со специальными символами.
  • Это может замедлить производительность - в моем случае версия IN использует сканирование индекса и диапазона, а версия REGEXP выполняет полное сканирование.

Ответ 22

Изучив различные решения на разных форумах и не найдя подходящего решения, я чувствую, что я получил нижеследующий взломан, проще всего следовать и кодировать:

Пример. Предположим, у вас есть несколько параметров для передачи в разделе "IN". Просто поместите фиктивную строку внутри предложения "IN", скажем, "PARAM" обозначают список параметров, которые будут появляться на месте этой фиктивной строки.

    select * from TABLE_A where ATTR IN (PARAM);

Вы можете собрать все параметры в одну переменную String в вашем Java-коде. Это можно сделать следующим образом:

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

Вы можете добавить все ваши параметры, разделенные запятыми, в одну переменную String, 'param1', в нашем случае.

После сбора всех параметров в одну строку вы можете просто заменить фиктивный текст в вашем запросе, то есть "PARAM" в этом случае, с параметром String, то есть параметром1. Вот что вам нужно сделать:

    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". Вы можете использовать комбинацию специальных символов и алфавитов вместо слова "PARAM", чтобы убедиться, что в запросе нет такого слова. Надеюсь, вы получили решение.

Примечание. Хотя это не подготовленный запрос, он выполняет ту работу, которую я хотел, чтобы мой код выполнял.

Ответ 23

Просто для полноты и потому, что я не видел, чтобы кто-нибудь еще предлагал это:

Прежде чем внедрять какие-либо сложные предложения, рассмотрите вопрос о том, действительно ли SQL-инъекция является проблемой в вашем сценарии.

Во многих случаях значение, предоставляемое IN (...), представляет собой список идентификаторов, которые были сгенерированы таким образом, что вы можете быть уверены, что инъекция невозможна... (например, результаты предыдущего select some_id from some_table где some_condition.)

Если это так, вы можете просто конкатенировать это значение и не использовать службы или подготовленный оператор для него или использовать их для других параметров этого запроса.

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

Ответ 24

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

Ответ 25

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

Это решение лучше, чем другие уродливые решения, в которых строка запроса построена с помощью ручных итераций

Ответ 26

Вы можете использовать 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
     */
}

Ответ 27

Я только что разработал для этого специфичную для 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($1, ',')::text[]);

Это даже достаточно гибко, чтобы поддерживать такие вещи, как сравнение LIKE:

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

Опять же, без сомнения, это взлом, но он работает и позволяет вам все еще использовать предварительно скомпилированные подготовленные операторы, которые принимают * ahem * дискретные параметры, с сопутствующими преимуществами безопасности и (возможно) производительности. Это целесообразно и действительно эффективно? Естественно, это зависит от того, как выполняется анализ строки и, возможно, приведение еще до того, как ваш запрос будет выполнен. Если вы ожидаете отправить три, пять, несколько десятков значений, конечно, это нормально. Несколько тысяч? Да, может быть, не так много. YMMV, ограничения и исключения применяются, никаких гарантий явных или подразумеваемых.

Но это работает.

Ответ 28

Мое обходное решение (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 - это массив, содержащий ваши входные/ключи/поля и т.д.