Quais são as melhores soluções para o uso de uma IN
cláusula SQL com instâncias de java.sql.PreparedStatement
, que não são suportadas por vários valores devido a problemas de segurança de ataques 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 (?)
O uso preparedStatement.setString( 1, "'A', 'B', 'C'" );
é essencialmente uma tentativa não-útil de uma solução alternativa dos motivos do uso ?
em primeiro lugar.
Quais soluções alternativas estão disponíveis?
Respostas:
Uma análise das várias opções disponíveis e os prós e contras de cada uma estão disponíveis aqui .
As opções sugeridas são:
SELECT my_column FROM my_table WHERE search_column = ?
, execute-o para cada valor e UNIONE os resultados do lado do cliente. Requer apenas uma declaração preparada. Lento e doloroso.SELECT my_column FROM my_table WHERE search_column IN (?,?,?)
e execute. Requer uma declaração preparada por tamanho da lista. Rápido e óbvio.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
e execute. [Ou useUNION ALL
no lugar desses pontos e vírgulas. --ed] Requer uma declaração preparada por tamanho da lista. Estupidamente lento, estritamente pior do queWHERE search_column IN (?,?,?)
, então não sei por que o blogueiro sugeriu isso.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
. Qualquer servidor decente otimizará os valores duplicados antes de executar a consulta.Nenhuma dessas opções é super ótima, no entanto.
Perguntas duplicadas foram respondidas nesses locais com alternativas igualmente saudáveis, mas nenhuma delas é ótima:
A resposta certa, se você estiver usando o JDBC4 e um servidor compatível
x = ANY(y)
, será usadoPreparedStatement.setArray
conforme descrito aqui:Parece não haver nenhuma maneira de
setArray
trabalhar com as listas IN.Às vezes, as instruções SQL são carregadas em tempo de execução (por exemplo, de um arquivo de propriedades), mas requerem um número variável de parâmetros. Nesses casos, primeiro defina a consulta:
Em seguida, carregue a consulta. Em seguida, determine o número de parâmetros antes de executá-lo. Depois que a contagem de parâmetros for conhecida, execute:
Por exemplo:
Para certos bancos de dados nos quais a passagem de uma matriz pela especificação JDBC 4 não é suportada, esse método pode facilitar a transformação da condição de cláusula lenta
= ?
em mais rápidaIN (?)
, que pode ser expandida chamando oany
método.fonte
Solução para PostgreSQL:
ou
fonte
.createArrayOf()
parte, mas não tenho certeza de que a semântica estrita para os usuáriosArray
seja definida pela especificação do JDBC..createArrayOf
não funcionar, você poderá criar sua própria criação manual de literal de matrizString arrayLiteral = "{A,\"B \", C,D}"
(observe que "B" possui um espaço enquanto C não funciona) e,statement.setString(1,arrayLiteral)
em seguida, onde a instrução preparada é... IN (SELECT UNNEST(?::VARCHAR[]))
ou... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PS: Eu não acho queANY
funciona com umSELECT
.)De maneira simples, AFAIK. Se o objetivo é manter a taxa de cache de instruções alta (ou seja, não criar uma instrução para cada contagem de parâmetros), você pode fazer o seguinte:
crie uma declaração com alguns (por exemplo, 10) parâmetros:
... ONDE ESTÁ (?,?,?,?,?,?,?,?,?, ...) ...
Vincular todos os parâmetros atuais
setString (1, "foo"); setString (2, "bar");
Vincule o restante como NULL
setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)
NULL nunca corresponde a nada; portanto, ele é otimizado pelo construtor de planos SQL.
A lógica é fácil de automatizar quando você passa uma Lista para uma função DAO:
fonte
NULL
na consulta corresponderia a umNULL
valor no banco de dados?NOT IN
eIN
não manipula nulos da mesma maneira. Execute isso e veja o que acontece:select 'Matched' as did_it_match where 1 not in (5, null);
remova onull
e observe a mágica.a IN (1,2,3,3,3,3,3)
é o mesmo quea IN (1,2,3)
. Também funciona comNOT IN
diferentementea NOT IN (1,2,3,null,null,null,null)
(que sempre retorna sem linhas, comoany_value != NULL
sempre é falso).Uma solução desagradável, mas certamente viável é usar uma consulta aninhada. Crie uma tabela temporária MYVALUES com uma coluna nela. Insira sua lista de valores na tabela MYVALUES. Então execute
Feia, mas uma alternativa viável se sua lista de valores for muito grande.
Essa técnica tem a vantagem adicional de otimizar planos de consulta potencialmente melhores do otimizador (verifique uma página em busca de vários valores, as tabelas podem apenas uma vez, uma vez por valor, etc.) pode economizar sobrecarga se o banco de dados não armazenar em cache instruções preparadas. Seu "INSERTS" precisaria ser feito em lote e a tabela MYVALUES poderá precisar ser ajustada para ter um bloqueio mínimo ou outras proteções de sobrecarga alta.
fonte
Limitações do operador in () são a raiz de todo mal.
Ele funciona em casos triviais, e você pode estendê-lo com "geração automática da instrução preparada", no entanto, está sempre tendo seus limites.
A abordagem in () pode ser boa o suficiente para alguns casos, mas não é à prova de foguetes :)
A solução à prova de foguetes é 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 visualização (ou qualquer outra maneira) para representá-los no SQL e usá-los no seu where critério.
Uma variante de força bruta está aqui http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
No entanto, se você pode usar o PL / SQL, essa bagunça pode se tornar bem organizada.
Em seguida, você pode passar um número arbitrário de IDs de clientes separados por vírgula no parâmetro e:
O truque aqui é:
A vista é semelhante a:
em que aux_in_list.getpayload se refere à sequência de entrada original.
Uma abordagem possível seria passar matrizes pl / sql (suportadas apenas pelo Oracle); no entanto, você não pode usá-las em SQL puro; portanto, sempre é necessária uma etapa de conversão. A conversão não pode ser feita no SQL; portanto, passar um clob com todos os parâmetros na string e convertê-lo em uma visualização é a solução mais eficiente.
fonte
Aqui está como eu o resolvi em meu próprio aplicativo. Idealmente, você deve usar um StringBuilder em vez de usar + para Strings.
Usar uma variável como x acima em vez de números concretos ajuda muito se você decidir alterar a consulta posteriormente.
fonte
Eu nunca tentei, mas faria .setArray () fazer o que você está procurando?
Atualização : Evidentemente não. O setArray parece funcionar apenas com um java.sql.Array que vem de uma coluna ARRAY que você recuperou de uma consulta anterior ou de uma subconsulta com uma coluna ARRAY.
fonte
Minha solução alternativa é:
Agora você pode usar uma variável para obter alguns valores em uma tabela:
Portanto, a declaração preparada pode ser:
Saudações,
Javier Ibanez
fonte
Suponho que você possa (usando a manipulação básica de strings) gerar a string de consulta
PreparedStatement
para ter um número?
correspondente ao número de itens em sua lista.É claro que se você estiver fazendo isso, estará a um passo de gerar um gigante encadeado
OR
em sua consulta, mas sem ter o número certo?
na sequência de consultas, não vejo como mais você pode contornar isso.fonte
Você pode usar o método setArray conforme mencionado neste javadoc :
fonte
Você pode usar
Collections.nCopies
para gerar uma coleção de espaços reservados e juntá-los usandoString.join
:fonte
Aqui está uma solução completa em Java para criar a declaração preparada para você:
fonte
O Spring permite passar java.util.Lists para NamedParameterJdbcTemplate , que automatiza a geração de (?,?,?, ...,?), Conforme apropriado para o número de argumentos.
Para Oracle, esta postagem no blog discute o uso de oracle.sql.ARRAY (Connection.createArrayOf não funciona com Oracle). Para isso, você deve modificar sua instrução SQL:
A função da tabela oracle transforma a matriz passada em uma tabela como valor utilizável na
IN
instruçãofonte
tente usar a função instr?
então
É certo que isso é um truque sujo, mas reduz as oportunidades de injeção de sql. Funciona no oracle de qualquer maneira.
fonte
O Sormula suporta o operador SQL IN, permitindo que você forneça um objeto java.util.Collection como parâmetro. Ele cria uma declaração preparada com um? para cada um dos elementos da coleção. Veja o Exemplo 4 (SQL no exemplo é um comentário para esclarecer o que é criado, mas não é usado pelo Sormula).
fonte
ao invés de usar
use a instrução SQL como
e
ou use um procedimento armazenado, essa seria a melhor solução, pois as instruções sql serão compiladas e armazenadas no servidor DataBase
fonte
Me deparei com uma série de limitações relacionadas à declaração preparada:
Entre as soluções propostas, eu escolheria aquela que não diminui o desempenho da consulta e faz o menor número de consultas. Esse será o número 4 (enviando poucas consultas) do link @Don ou especificando valores NULL para '?' Desnecessários marcas como proposto por @Vladimir Dyuzhev
fonte
Acabei de elaborar uma opção específica do PostgreSQL para isso. É um pouco complicado, e vem com seus próprios prós, contras e limitações, mas parece funcionar e não está limitado a uma linguagem de desenvolvimento, plataforma ou driver PG específico.
O truque, é claro, é encontrar uma maneira de passar uma coleção arbitrária de valores como um único parâmetro e fazer com que o banco de dados reconheça isso como vários valores. A solução que eu estou trabalhando é construir uma string delimitada a partir dos valores da coleção, passar essa string como um único parâmetro e usar string_to_array () com a conversão necessária para o PostgreSQL utilizá-la corretamente.
Portanto, se você deseja procurar por "foo", "blá" e "abc", concatená-los juntos em uma única string como: 'foo, blá, abc'. Aqui está o SQL direto:
Você obviamente alteraria a conversão explícita para o que você deseja que sua matriz de valores resultante seja - int, texto, uuid etc. também), você pode passá-lo como parâmetro em uma instrução preparada:
Isso é flexível o suficiente para suportar coisas como comparações LIKE:
Novamente, sem dúvida é um hack, mas funciona e permite que você ainda use instruções preparadas pré-compiladas que usam parâmetros distintos * ahem * , com os benefícios de segurança e (talvez) desempenho associados. É aconselhável e realmente tem desempenho? Naturalmente, isso depende, pois você tem a análise de string e a transmissão possível antes mesmo de sua consulta ser executada. Se você espera enviar três, cinco, algumas dezenas de valores, claro, provavelmente tudo bem. Alguns milhares? Sim, talvez não tanto. YMMV, limitações e exclusões se aplicam, nenhuma garantia expressa ou implícita.
Mas funciona.
fonte
Apenas para ser completo: contanto que o conjunto de valores não seja muito grande, você também pode simplesmente construir uma string como uma instrução como
que você pode passar para preparar () e usar setXXX () em um loop para definir todos os valores. Isso parece nojento, mas muitos "grandes" sistemas comerciais fazem esse tipo de coisa rotineiramente até atingirem limites específicos do banco de dados, como 32 KB (acho que é) para declarações no Oracle.
É claro que você precisa garantir que o conjunto nunca seja excessivamente grande ou faça uma captura de erros no caso em que é.
fonte
Seguindo a ideia de Adam. Faça com que sua declaração preparada selecione minha_coluna de minha_tabela, onde pesquisa_coluna em (#) Crie uma String x e preencha-a com um número de "?,?,?" dependendo da sua lista de valores Em seguida, basta alterar o # na consulta para sua nova String x e preencher
fonte
Gere a string de consulta no PreparedStatement para que um número de? Corresponda ao número de itens em sua lista. Aqui está um exemplo:
fonte
StringBuilder
. Mas não da maneira que você pensa. Ao descompilar,generateQsForIn
é possível ver que duas novas iterações por loopStringBuilder
são alocadas etoString
são chamadas em cada uma. AStringBuilder
otimização captura apenas coisas como,"x" + i+ "y" + j
mas não se estende além de uma expressão.ps.setObject(1,items)
vez de iterar na lista e depois definir oparamteres
?Existem diferentes abordagens alternativas que podemos usar para a cláusula IN no PreparedStatement.
Use NULL em consultas PreparedStatement - desempenho ideal, funciona muito bem quando você conhece o limite dos argumentos da cláusula IN. Se não houver limite, você poderá executar consultas em lote. O trecho de código de amostra é;
Você pode verificar mais detalhes sobre essas abordagens alternativas aqui .
fonte
Para algumas situações, o regexp pode ajudar. Aqui está um exemplo que verifiquei no Oracle e funciona.
Mas há várias desvantagens:
fonte
Depois de examinar várias soluções em diferentes fóruns e não encontrar uma boa solução, considero o hack abaixo que eu criei, o mais fácil de seguir e codificar:
Exemplo: suponha que você tenha vários parâmetros para passar na cláusula 'IN'. Basta colocar uma String fictícia dentro da cláusula 'IN', por exemplo, "PARAM" denota a lista de parâmetros que virão no lugar dessa String fictícia.
Você pode coletar todos os parâmetros em uma única variável String no seu código Java. Isso pode ser feito da seguinte forma:
Você pode anexar todos os seus parâmetros separados por vírgulas em uma única variável String, 'param1', no nosso caso.
Após coletar todos os parâmetros em uma única String, você pode simplesmente substituir o texto fictício em sua consulta, ou seja, "PARAM" nesse caso, pelo parâmetro String, ou seja, param1. Aqui está o que você precisa fazer:
Agora você pode executar sua consulta usando o método executeQuery (). Apenas certifique-se de que você não tenha a palavra "PARAM" na sua consulta em nenhum lugar. Você pode usar uma combinação de caracteres especiais e alfabetos em vez da palavra "PARAM" para garantir que não exista a possibilidade de uma palavra aparecer na consulta. Espero que você tenha a solução.
Nota: Embora essa não seja uma consulta preparada, ela faz o trabalho que eu queria que meu código fizesse.
fonte
Só por completude e porque não vi mais ninguém sugerir:
Antes de implementar qualquer uma das sugestões complicadas acima, considere se a injeção de SQL é realmente um problema no seu cenário.
Em muitos casos, o valor fornecido para IN (...) é uma lista de IDs que foram gerados de maneira a garantir que nenhuma injeção seja possível ... (por exemplo, os resultados de uma seleção anterior, some_id de some_table em que alguma_condição.)
Se for esse o caso, você pode apenas concatenar esse valor e não usar os serviços ou a instrução preparada para ele ou usá-los para outros parâmetros desta consulta.
fonte
PreparedStatement não fornece uma 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 itens que devem se tornar parte da instrução SQL. Isso é necessário porque, se o próprio SQL puder mudar, o o driver não pode pré-compilar a instrução. Ele também tem o bom efeito colateral de impedir ataques de injeção SQL ". Acabei usando a seguinte abordagem:
fonte
O SetArray é a melhor solução, mas não está disponível para muitos drivers mais antigos. A seguinte solução alternativa pode ser usada no java8
Essa solução é melhor que outras soluções feias de loop em que a string de consulta é criada por iterações manuais
fonte
Isso funcionou para mim (psuedocode):
especificar ligação:
fonte
Meu exemplo para bancos de dados SQLite e Oracle.
O primeiro loop For é para criar objetos PreparedStatement.
O segundo loop For é para Fornecer valores para parâmetros PreparedStatement.
fonte
Minha solução alternativa (JavaScript)
SearchTerms
é a matriz que contém suas entradas / chaves / campos, etc.fonte