Pergunta

Quais são as melhores soluções alternativas para usar uma cláusula IN SQL com instâncias de java.sql.PreparedStatement, que não é suportado por vários valores, devido a questões de segurança ataque de injeção SQL:. Um espaço reservado ? representa um valor, em vez de uma lista de valores

Considere a seguinte instrução SQL:

SELECT my_column FROM my_table where search_column IN (?)

Usando preparedStatement.setString( 1, "'A', 'B', 'C'" ); é essencialmente uma tentativa não-trabalhando em uma solução alternativa das razões para a utilização ? em primeiro lugar.

O que soluções alternativas estão disponíveis?

Foi útil?

Solução

Uma análise das várias opções disponíveis e os prós e contras de cada um está disponível aqui .

As opções sugeridas são:

  • Preparar SELECT my_column FROM my_table WHERE search_column = ?, executá-lo para cada valor e UNION os resultados do lado do cliente. Requer apenas uma declaração preparada. Lenta e dolorosa.
  • Preparar SELECT my_column FROM my_table WHERE search_column IN (?,?,?) e executá-lo. Requer uma declaração preparada por tamanho-de-IN-lista. Rápido e óbvio.
  • Preparar SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... e executá-lo. [Ou uso UNION ALL no lugar daqueles ponto e vírgula. --ed] Requer uma declaração preparada por tamanho-de-IN-lista. Estupidamente lento, estritamente pior do que WHERE search_column IN (?,?,?), então eu não sei por que o blogueiro até sugeriu ele.
  • Use um procedimento armazenado para construir o conjunto de resultados.
  • Preparar N consultas diferentes em tamanho de-IN-list; dizer, com 2, 10 e 50 valores. Para procurar uma lista IN com 6 valores diferentes, preencher a consulta tamanho-10 para que ele se parece com SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6). Qualquer servidor decente vai otimizar os valores duplicados antes de executar a consulta.

Nenhuma destas opções são super grande, no entanto.

perguntas duplicadas foram respondidas nestes locais com alternativas igualmente sãs, ainda nenhum deles super-grande:

A resposta certa, se você estiver usando JDBC4 e um servidor que suporta x = ANY(y), é usar PreparedStatement.setArray como descrito aqui:

Não parece haver nenhuma maneira de tornar o trabalho setArray com listas IN, no entanto.


declarações Às vezes SQL são carregados em tempo de execução (por exemplo, a partir de um arquivo de propriedades), mas necessita de um número variável de parâmetros. Em tais casos, primeiro definir a consulta:

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

Em seguida, carregar a consulta. Em seguida, determinar o número de parâmetros antes de executá-lo. Uma vez que a contagem de parâmetro é conhecido, execute:

sql = any( sql, count );

Por exemplo:

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

Para certas bases de dados onde passar uma matriz através da especificação JDBC 4 é suportado, este método pode facilitar a transformação do = ? lento na condição cláusula mais rápido IN (?), que pode então ser expandida chamando o método any.

Outras dicas

Solução para PostgreSQL:

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table where search_column = ANY (?)"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

ou

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table " + 
        "where search_column IN (SELECT * FROM unnest(?))"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

Sem simples maneira AFAIK. Se o alvo é manter declaração cache de alta relação (ou seja para não criar uma declaração por cada contagem de parâmetro), você pode fazer o seguinte:

  1. criar uma declaração com alguns (por exemplo 10) parâmetros:

    ... ONDE A IN (?,?,?,?,?,?,?,?,?,?) ...

  2. Bind todos actuall parâmetros

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

  3. Bind o resto como NULL

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

NULL não corresponde a qualquer coisa, por isso fica otimizado pelo construtor de plano de SQL.

A lógica é fácil de automatizar quando você passar uma lista para uma função DAO:

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

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

Uma forma de contornar desagradável, mas certamente viável é a utilização de uma consulta aninhada. Criar um MyValues ??tabela temporária com uma coluna na mesma. Insira sua lista de valores na tabela de MyValues. Em seguida, executar

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

feio, mas uma alternativa viável se a sua lista de valores é muito grande.

Esta técnica tem a vantagem de potencialmente melhores planos de consulta do Optimizer (verificar uma página para vários valores, tablescan apenas uma vez, em vez de uma vez por valor, etc) pode economizar em cima se o seu banco de dados não declarações de cache preparadas. Suas "Inserções" precisaria ser feito em lotes e tabela de MyValues ??pode precisar de ser ajustado para ter bloqueio mínimo ou outras proteções de alta sobrecarga.

Limitações do operador In () é a raiz de todo o mal.

Ele funciona para casos triviais, e você pode estendê-lo com "geração automática da declaração preparada" no entanto, é ter sempre os seus limites.

  • se você estiver criando uma declaração com número variável de parâmetros, que vai fazer uma sobrecarga sql de análise em cada chamada
  • em várias plataformas, o número de parâmetros de no operador () são limitados
  • em todas as plataformas, o tamanho total do texto SQL é limitada, tornando impossível para enviar para baixo 2000 espaços reservados para o em params
  • enviando abaixo variáveis ??de ligação de 1000-10k não é possível, como o driver JDBC está a ter suas limitações

O in () abordagem pode ser bom o suficiente para alguns casos, mas não uma prova de foguetes:)

A solução à prova de foguete é para passar o número arbitrário de parâmetros em uma chamada separada (passando um clob de parâmetros, por exemplo), e, em seguida, ter uma visão (ou qualquer outra forma) para representá-los em SQL e uso em sua onde os critérios.

Uma variante de força bruta é aqui http: //tkyte.blogspot .hu / 2006/06 / variando-in-lists.html

No entanto, se você pode usar PL / SQL, essa confusão pode se tornar muito arrumado.

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;

Em seguida, você pode passar número arbitrário de valores separados por vírgulas ids de clientes no parâmetro, e:

  • terá nenhum atraso de análise, como o SQL para selecionar é estável
  • sem funções em pipeline complexidade - é apenas uma consulta
  • o SQL está usando uma junção simples, em vez de um operador IN, que é bastante rápido
  • afinal de contas, é uma boa regra de não bater o banco de dados com qualquer seleto simples ou DML, uma vez que é Oracle, que oferece anos-luz de mais de MySQL ou bancos de dados simples semelhantes. PL / SQL permite ocultar o modelo de armazenamento do seu modelo de domínio da aplicação de uma forma eficaz.

O truque aqui é:

  • precisamos de uma chamada que aceita a cadeia longa, e armazenar em algum lugar onde a sessão db pode acessar a ele (variável de pacote por exemplo simples, ou DBMS_SESSION.SET_CONTEXT)
  • então precisamos de uma visão que pode analisar isso para linhas
  • e, em seguida, você tem uma vista que contém os ids que você está consultando, então tudo que você precisa é um simples juntar-se à mesa consultado.

A vista se parece com:

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

onde aux_in_list.getpayload refere-se à seqüência de entrada original.


Uma abordagem possível seria passar pl / sql matrizes (suportado apenas por Oracle), porém você não pode usar aqueles em SQL puro, portanto, um passo de conversão é sempre necessária. A conversão não pode ser feito em SQL, por isso, depois de tudo, passando por uma clob com todos os parâmetros em corda e convertê-lo witin uma vista é a solução mais eficiente.

Eu nunca tentei, mas .setArray () fazer o que você está procurando?

Atualizar : Evidentemente não. setArray só parece funcionar com um java.sql.Array que vem de uma coluna de matriz que você já recuperado de uma consulta anterior, ou uma subconsulta com uma coluna de matriz.

A minha solução é:

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

Agora você pode usar uma variável para obter alguns valores em uma tabela:

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

Assim, a declaração preparada pode ser:

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

Saudações,

Javier Ibanez

Aqui está como eu resolvido isso na minha própria aplicação. Idealmente, você deve usar um StringBuilder em vez de usar + para Strings.

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

Usando uma variável como x acima em vez de números concretos ajuda muito se você decidir mudar a consulta em um momento posterior.

Eu acho que você pode (usando manipulação básica string) gerar a string de consulta na PreparedStatement ter um número de ? de combinar o número de itens em sua lista.

É claro que se você está fazendo que você é apenas um passo de gerar uma OR acorrentado gigante em sua consulta, mas sem ter o número certo de ? na cadeia de consulta, não vejo de que outra forma você pode trabalho em torno deste.

Você pode usar o método setArray conforme mencionado em este 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();

tente usar a função instr?

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

então

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

É certo que este é um pouco de um truque sujo, mas reduz as oportunidades de injeção de SQL. Obras em oráculo de qualquer maneira.

Sormula suporta um operador SQL IN, permitindo-lhe fornecer um objeto java.util.Collection como um parâmetro. Ele cria uma declaração preparada com um? para cada um dos elementos da recolha. Consulte Exemplo 4 (SQL no exemplo é um comentário para esclarecer o que é criado, mas não é usado por Sormula) .

em vez de usar

SELECT my_column FROM my_table where search_column IN (?)

Use a instrução SQL como

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

e

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

ou usar um procedimento armazenado esta seria a melhor solução, uma vez que as instruções SQL serão compilados e armazenados no servidor de banco de dados

me deparei com uma série de limitações relacionadas com a declaração preparada:

  1. As declarações preparadas são armazenadas em cache somente dentro da mesma sessão (Postgres), por isso vai realmente funcionar apenas com o pool de conexões
  2. Um monte de diferentes declarações preparadas como proposto por @BalusC pode causar o cache para transbordo e declarações anteriormente em cache será descartado
  3. A consulta tem de ser optimizada e os índices de uso. Parece evidente, no entanto, por exemplo, a declaração ANY (ARRAY ...) proposto por @Boris em uma das principais respostas não pode usar índices e consulta será lento apesar de cache
  4. A declaração preparada armazena em cache o plano de consulta tão bem e os valores reais dos parâmetros especificados na instrução não estão disponíveis.

Entre as soluções propostas gostaria de escolher o que não diminui o desempenho da consulta e faz o menor número de consultas. Este será o (algumas consultas de porção) # 4 a partir do link @Don ou especificando valores NULL para desnecessários '?' marcas como proposto por @Vladimir Dyuzhev

Aqui está uma solução completa em Java para criar a declaração preparada para você:

/*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 permite passando java.util.Lists para NamedParameterJdbcTemplate , que automatiza a geração de (?,?,?, ...,?), conforme apropriado para o número de argumentos.

Para Oracle, desta postagem do blog discute o uso de oracle.sql.ARRAY (não Connection.createArrayOf não funcionar com Oracle). Para isso, você tem que modificar a instrução SQL:

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

função Oracle tabela transforma a matriz passada em uma tabela como utilizável valor na declaração IN.

Apenas para ser completo: Enquanto o conjunto de valores não é muito grande, você pode também simplesmente string de construir uma declaração como

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

que você poderia então passar para preparar (), e depois usar setXXX () em um loop para definir todos os valores. Isso parece nojento, mas muitos "grandes" sistemas comerciais rotineiramente fazer esse tipo de coisa, até que atingiu limites específicos-DB, tais como 32 KB (acho que é) para as demonstrações em Oracle.

Claro que você precisa para garantir que o conjunto nunca será exageradamente grande, ou véu de erro no caso em que ele é.

A seguir a idéia de Adam. Faça o seu tipo preparado declaração de select my_column de my_table onde search_column em (#) Crie uma String X e preenchê-lo com uma série de "?,?,?" dependendo da sua lista de valores Em seguida, basta mudar o # na consulta para o seu new String x um preenchimento

Gerar a seqüência de consulta no PreparedStatement para ter um número de? 'S combinando o número de itens em sua lista. Aqui está um exemplo:

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

Existem diferentes abordagens alternativas que podemos usar na cláusula no PreparedStatement.

  1. Usar o Single Consultas - desempenho mais lento e recurso intensivo
  2. Usando StoredProcedure - mais rápido, mas específica do banco de dados
  3. Criação de consulta dinâmica para PreparedStatement -. Bom desempenho, mas não recebe benefício de caching e PreparedStatement é recompilado cada vez
  4. Use NULL em PreparedStatement consulta - O desempenho ideal, funciona muito bem quando você sabe o limite de argumentos cláusula. Se não há limite, então você pode executar consultas em lote. trecho de código de exemplo é;

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

Você pode conferir mais detalhes sobre estes abordagens alternativas href="http://www.journaldev.com/2521/jdbc-preparedstatement-in-clause-alternative-approaches" aqui .

Para algumas situações de expressões regulares pode ajudar. Aqui está um exemplo que eu verifiquei em Oracle, e ele funciona.

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

Mas há uma série de inconvenientes com ele:

  1. Qualquer coluna que aplicada deve ser convertido para VARCHAR / char, pelo menos implicitamente.
  2. precisa ter cuidado com caracteres especiais.
  3. Pode abrandar o desempenho -. No meu caso no índice usa a versão e faixa de varredura, ea versão REGEXP fazer varredura completa

Depois de examinar várias soluções em diferentes fóruns e não encontrar uma boa solução, eu sinto a seguir cortar eu vim com, é o mais fácil de seguir e código:

Exemplo: Suponha que você tenha múltiplos parâmetros para passar na cláusula 'IN'. Basta colocar uma String manequim dentro cláusula do 'IN', digamos, "PARAM" fazer denotar a lista de parâmetros que virão no lugar desta cadeia manequim.

    select * from TABLE_A where ATTR IN (PARAM);

Você pode coletar todos os parâmetros em uma única variável no seu código Java. Isso pode ser feito da seguinte forma:

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

Você pode acrescentar todos os seus parâmetros separados por vírgulas em uma única variável String, 'param1', no nosso caso.

Depois de recolher todos os parâmetros em uma única seqüência que você pode simplesmente substituir o texto fictício em sua consulta, ou seja, "PARAM", neste caso, com o parâmetro String, ou seja, param1. Aqui está o que você precisa fazer:

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

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

Agora você pode executar a consulta usando o método executeQuery (). Apenas certifique-se que você não tem a palavra "PARAM" em seu lugar consulta. Você pode usar uma combinação de caracteres especiais e alfabetos em vez da palavra "PARAM", a fim de se certificar de que não há nenhuma possibilidade de palavra tal vindo na consulta. Espero que você tenha a solução.

Nota:. Embora esta não é uma consulta preparada, ele faz o trabalho que eu queria que meu código para fazer

Apenas para a integralidade e porque eu não vi mais ninguém sugerir isso:

Antes de implementar qualquer uma das sugestões complicadas acima considerar se a injeção de SQL é realmente um problema em seu cenário.

Em muitos casos, o valor fornecido a IN (...) é uma lista de ids que foram gerados de uma forma que você pode ter certeza de que nenhuma injeção é possível ... (por exemplo, os resultados de uma seleção some_id anterior de some_table onde some_condition.)

Se for esse o caso, você pode simplesmente concatenar este valor e não utilizar os serviços ou a instrução preparada para ele ou usá-los para outros parâmetros desta consulta.

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

PreparedStatement não fornece nenhuma boa maneira de lidar com a cláusula SQL IN. Por http://www.javaranch.com/journal/200510/Journal200510.jsp # a2 "Você não pode substituir as coisas que se destinam a tornar-se parte da instrução SQL. Isso é necessário porque se o próprio SQL pode mudar, o motorista pode não precompile o comunicado. Ele também tem o lado bom efeito de impedir ataques de injeção de SQL ". Acabei usando seguinte abordagem:

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 é a melhor solução, mas não é disponível para muitos motoristas mais velhos. A seguinte solução pode ser usada em 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);
}

Esta solução é melhor do que outras soluções loop while feias onde a string de consulta é construído por iterações manuais

Você pode usar Collections.nCopies para gerar um conjunto de espaços reservados e juntá-los usando 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
     */
}

Eu só funcionou uma opção específica de PostgreSQL para isso. É um pouco de um truque, e vem com suas próprias vantagens e desvantagens e limitações, mas parece trabalho e não se limita a uma linguagem de desenvolvimento específico, plataforma ou motorista PG.

O truque, claro, é encontrar uma maneira de passar uma coleção comprimento arbitrário de valores como um parâmetro único, e ter o db reconhecê-lo como vários valores. A solução que eu trabalho é construir uma cadeia delimitada a partir dos valores na coleção, passar essa string como um único parâmetro e uso string_to_array () com o elenco requisito para PostgreSQL para fazer corretamente o uso do mesmo.

Então, se você quiser procurar por "foo", "blah", e "abc", que você pode concatenar-los juntos em uma única string como: 'foo, blá, abc'. Aqui está o SQL reta:

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

Você, obviamente, alterar a conversão explícita para o que você queria sua matriz valor resultante ser - int, texto, uuid, etc. E porque a função está tomando um único valor da cadeia (ou dois eu suponho, se você quiser personalizar o delimitador também), você pode passá-lo como um parâmetro em uma declaração preparada:

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

Isto é ainda bastante flexível para as coisas de apoio como comparações como:

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

Mais uma vez, não há dúvida que é um hack, mas funciona e permite ainda usar declarações preparadas pré-compilados que take * ahem * parâmetros discretos, com a segurança que acompanha e (talvez) benefícios de desempenho . É aconselhável e realmente alto desempenho? Naturalmente, isso depende, como você tem corda analisar e, possivelmente, lançando acontecendo antes de sua consulta ainda corre. Se você está esperando para enviar três, cinco, alguns valores dúzia, com certeza, é provavelmente bem. Alguns milhares? Sim, talvez não tanto. YMMV, limitações e exclusões, nenhuma garantia expressa ou implícita.

Mas ele funciona.

Minha solução (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 é a matriz que contém suas entradas / chaves / campos etc

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top