Apa solusi terbaik untuk menggunakan IN
klausa SQL dengan contoh java.sql.PreparedStatement
, yang tidak didukung untuk beberapa nilai karena masalah keamanan serangan injeksi SQL: Satu ?
placeholder mewakili satu nilai, bukan daftar nilai.
Pertimbangkan pernyataan SQL berikut:
SELECT my_column FROM my_table where search_column IN (?)
Penggunaan preparedStatement.setString( 1, "'A', 'B', 'C'" );
pada dasarnya adalah upaya yang tidak bekerja untuk mencari tahu alasan penggunaannya ?
.
Solusi apa yang tersedia?
Jawaban:
Analisis berbagai opsi yang tersedia, dan pro dan kontra dari masing-masing tersedia di sini .
Opsi yang disarankan adalah:
SELECT my_column FROM my_table WHERE search_column = ?
, melaksanakannya untuk setiap nilai dan UNION hasil sisi klien. Hanya membutuhkan satu pernyataan yang disiapkan. Lambat dan menyakitkan.SELECT my_column FROM my_table WHERE search_column IN (?,?,?)
dan melaksanakannya. Membutuhkan satu pernyataan yang disiapkan per ukuran-dalam-daftar. Cepat dan jelas.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
dan melaksanakannya. [Atau gunakanUNION ALL
sebagai pengganti titik koma itu. --ed] Membutuhkan satu pernyataan yang disiapkan per ukuran-dalam-daftar. Sangat lambat, benar-benar lebih buruk daripadaWHERE search_column IN (?,?,?)
, jadi saya tidak tahu mengapa blogger bahkan menyarankannya.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
. Server mana pun yang layak akan mengoptimalkan nilai duplikat sebelum menjalankan kueri.Namun, tidak satu pun dari opsi ini yang sangat bagus.
Pertanyaan duplikat telah dijawab di tempat-tempat ini dengan alternatif yang sama-sama waras, masih tidak ada yang super hebat:
Jawaban yang Benar, jika Anda menggunakan JDBC4 dan server yang mendukung
x = ANY(y)
, adalah untuk digunakanPreparedStatement.setArray
seperti yang dijelaskan di sini:Sepertinya tidak ada cara untuk membuat
setArray
pekerjaan dengan IN-list.Kadang-kadang pernyataan SQL dimuat saat runtime (misalnya, dari file properti) tetapi memerlukan sejumlah variabel parameter. Dalam kasus seperti itu, tentukan terlebih dahulu kueri:
Selanjutnya, muat kueri. Kemudian tentukan jumlah parameter sebelum menjalankannya. Setelah jumlah parameter diketahui, jalankan:
Sebagai contoh:
Untuk database tertentu di mana melewatkan array melalui spesifikasi JDBC 4 tidak didukung, metode ini dapat memfasilitasi mengubah lambat
= ?
menjadiIN (?)
kondisi klausa yang lebih cepat , yang kemudian dapat diperluas dengan memanggilany
metode.sumber
Solusi untuk PostgreSQL:
atau
sumber
.createArrayOf()
bagian itu, tetapi saya tidak yakin semantik ketat untuk penggunaArray
ditentukan oleh spesifikasi JDBC..createArrayOf
tidak berhasil, Anda dapat melakukan pembuatan manual sendiri seperti array literalString arrayLiteral = "{A,\"B \", C,D}"
(perhatikan bahwa "B" memiliki spasi sementara C tidak) dan kemudian distatement.setString(1,arrayLiteral)
mana pernyataan yang disiapkan adalah... IN (SELECT UNNEST(?::VARCHAR[]))
atau... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PS: Saya kira tidakANY
bekerja denganSELECT
.)Tidak ada cara sederhana AFAIK. Jika target adalah untuk menjaga rasio cache pernyataan tetap tinggi (yaitu untuk tidak membuat pernyataan per setiap jumlah parameter), Anda dapat melakukan hal berikut:
buat pernyataan dengan beberapa (misalnya 10) parameter:
... DI MANA SAJA (?,?,?,?,?,?,?,?,?,?) ...
Bind semua parameter aktual
setString (1, "foo"); setString (2, "bar");
Bind sisanya sebagai NULL
setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)
NULL tidak pernah cocok dengan apa pun, sehingga dioptimalkan oleh pembuat paket SQL.
Logikanya mudah untuk diotomatisasi ketika Anda memasukkan Daftar ke fungsi DAO:
sumber
NULL
dalam kueri cocok denganNULL
nilai dalam database?NOT IN
danIN
tidak menangani nulls dengan cara yang sama. Jalankan ini dan lihat apa yang terjadi:select 'Matched' as did_it_match where 1 not in (5, null);
Kemudian lepaskannull
dan tonton keajaibannya.a IN (1,2,3,3,3,3,3)
juga dengana IN (1,2,3)
. Ini juga berfungsi denganNOT IN
tidak sepertia NOT IN (1,2,3,null,null,null,null)
(yang selalu tidak mengembalikan baris sepertiany_value != NULL
yang selalu salah).Solusi yang tidak menyenangkan, tetapi tentu layak dilakukan dengan menggunakan kueri bersarang. Buat tabel sementara MYVALUES dengan kolom di dalamnya. Masukkan daftar nilai Anda ke dalam tabel MYVALUES. Kemudian jalankan
Jelek, tetapi merupakan alternatif yang layak jika daftar nilai Anda sangat besar.
Teknik ini memiliki keuntungan tambahan dari rencana kueri yang berpotensi lebih baik dari optimizer (periksa halaman untuk beberapa nilai, tablescan hanya sekali sebagai gantinya sekali per nilai, dll) dapat menghemat overhead jika database Anda tidak men-cache pernyataan yang disiapkan. "INSERTS" Anda harus dilakukan dalam batch dan tabel MYVALUES mungkin perlu diubah agar memiliki penguncian minimal atau perlindungan overhead tinggi lainnya.
sumber
Keterbatasan operator in () adalah akar dari semua kejahatan.
Ini bekerja untuk kasus-kasus sepele, dan Anda dapat memperpanjangnya dengan "generasi otomatis dari pernyataan yang disiapkan" namun selalu ada batasnya.
Pendekatan in () bisa cukup baik untuk beberapa kasus, tetapi bukan bukti roket :)
Solusi roket-bukti adalah untuk melewati jumlah parameter yang sewenang-wenang dalam panggilan terpisah (misalnya, dengan mengirimkan sekelompok params), dan kemudian memiliki pandangan (atau cara lain) untuk mewakili mereka dalam SQL dan digunakan di mana Anda kriteria.
Varian brute-force ada di sini http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
Namun jika Anda bisa menggunakan PL / SQL, kekacauan ini bisa menjadi sangat rapi.
Kemudian, Anda dapat melewati jumlah id pelanggan yang dipisahkan koma di parameter, dan:
Kuncinya di sini adalah:
Tampilannya seperti:
di mana aux_in_list.getpayload merujuk ke string input asli.
Sebuah pendekatan yang mungkin adalah dengan melewatkan array pl / sql (hanya didukung oleh Oracle), namun Anda tidak dapat menggunakannya dalam SQL murni, oleh karena itu langkah konversi selalu diperlukan. Konversi tidak dapat dilakukan dalam SQL, jadi setelah semua, melewati sebuah clob dengan semua parameter dalam string dan mengubahnya dalam tampilan adalah solusi yang paling efisien.
sumber
Inilah cara saya menyelesaikannya di aplikasi saya sendiri. Idealnya, Anda harus menggunakan StringBuilder alih-alih menggunakan + untuk Strings.
Menggunakan variabel seperti x di atas sebagai ganti angka nyata sangat membantu jika Anda memutuskan untuk mengubah kueri di lain waktu.
sumber
Saya belum pernah mencobanya, tetapi apakah .setArray () akan melakukan apa yang Anda cari?
Pembaruan : Jelas tidak. setArray tampaknya hanya berfungsi dengan java.sql.Array yang berasal dari kolom ARRAY yang telah Anda ambil dari kueri sebelumnya, atau subquery dengan kolom ARRAY.
sumber
Solusi saya adalah:
Sekarang Anda bisa menggunakan satu variabel untuk mendapatkan beberapa nilai dalam tabel:
Jadi, pernyataan yang disiapkan bisa berupa:
Salam,
Javier Ibanez
sumber
Saya kira Anda bisa (menggunakan manipulasi string dasar) menghasilkan string kueri
PreparedStatement
untuk memiliki sejumlah yang?
cocok dengan jumlah item dalam daftar Anda.Tentu saja jika Anda melakukan itu Anda hanya selangkah lagi dari menghasilkan raksasa dirantai
OR
dalam permintaan Anda, tetapi tanpa memiliki jumlah yang tepat?
dalam string kueri, saya tidak melihat bagaimana lagi Anda bisa mengatasi ini.sumber
Anda bisa menggunakan metode setArray seperti yang disebutkan di javadoc ini :
sumber
Anda dapat menggunakan
Collections.nCopies
untuk menghasilkan koleksi placeholder dan bergabung dengan mereka menggunakanString.join
:sumber
Berikut solusi lengkap di Jawa untuk membuat pernyataan yang disiapkan untuk Anda:
sumber
Spring memungkinkan lewat java.util.Lists ke NamedParameterJdbcTemplate , yang mengotomatiskan generasi (?,?,?, ...,?), Yang sesuai untuk jumlah argumen.
Untuk Oracle, posting blog ini membahas penggunaan oracle.sql.ARRAY (Connection.createArrayOf tidak berfungsi dengan Oracle). Untuk ini, Anda harus mengubah pernyataan SQL Anda:
The tabel fungsi oracle mengubah array berlalu ke dalam tabel seperti nilai yang dapat digunakan dalam
IN
pernyataan.sumber
coba gunakan fungsi instr?
kemudian
Memang ini sedikit hack kotor, tapi itu mengurangi peluang untuk injeksi sql. Tetap bekerja di oracle.
sumber
Sormula mendukung operator SQL IN dengan memungkinkan Anda untuk memasok objek java.util.Collection sebagai parameter. Itu menciptakan pernyataan siap dengan? untuk setiap elemen koleksi. Lihat Contoh 4 (SQL dalam contohnya adalah komentar untuk menjelaskan apa yang dibuat tetapi tidak digunakan oleh Sormula).
sumber
alih-alih menggunakan
gunakan Pernyataan Sql sebagai
dan
atau menggunakan prosedur tersimpan ini akan menjadi solusi terbaik, karena pernyataan sql akan dikompilasi dan disimpan di server DataBase
sumber
Saya menemukan sejumlah batasan terkait dengan pernyataan yang disiapkan:
Di antara solusi yang diajukan, saya akan memilih salah satu yang tidak mengurangi kinerja kueri dan membuat jumlah kueri yang lebih sedikit. Ini akan menjadi # 4 (mengumpulkan beberapa pertanyaan) dari tautan @Don atau menentukan nilai NULL untuk yang tidak dibutuhkan '?' menandai seperti yang diusulkan oleh @Vladimir Dyuzhev
sumber
Saya baru saja mengerjakan opsi khusus PostgreSQL untuk ini. Ini sedikit peretasan, dan dilengkapi dengan pro dan kontra dan batasannya sendiri, tetapi tampaknya berfungsi dan tidak terbatas pada bahasa pengembangan, platform, atau driver PG tertentu.
Triknya tentu saja adalah menemukan cara untuk melewatkan koleksi panjang nilai yang berubah-ubah sebagai parameter tunggal, dan minta db mengenalinya sebagai beberapa nilai. Solusi yang saya kerjakan adalah membangun string yang dibatasi dari nilai-nilai dalam koleksi, meneruskan string itu sebagai parameter tunggal, dan menggunakan string_to_array () dengan casting yang diperlukan untuk PostgreSQL untuk memanfaatkannya dengan benar.
Jadi jika Anda ingin mencari "foo", "bla", dan "abc", Anda dapat menyatukannya menjadi satu string sebagai: 'foo, blah, abc'. Inilah SQL lurus:
Anda jelas akan mengubah pemeran eksplisit menjadi apa pun yang Anda inginkan menjadi array nilai yang dihasilkan menjadi - int, teks, uuid, dll. Dan karena fungsinya mengambil nilai string tunggal (atau dua saya kira, jika Anda ingin menyesuaikan pembatas juga), Anda dapat meneruskannya sebagai parameter dalam pernyataan yang disiapkan:
Ini bahkan cukup fleksibel untuk mendukung hal-hal seperti perbandingan SEPERTI:
Sekali lagi, tidak diragukan lagi ini adalah peretasan, tetapi ini berfungsi dan memungkinkan Anda untuk tetap menggunakan pernyataan yang telah disiapkan sebelumnya yang mengambil parameter diskrit * ahem * , dengan keamanan yang menyertainya dan (mungkin) manfaat kinerja. Apakah disarankan dan benar-benar performant? Tentu saja, itu tergantung, karena Anda memiliki penguraian string dan kemungkinan casting berlangsung sebelum kueri Anda bahkan berjalan. Jika Anda mengharapkan untuk mengirim tiga, lima, beberapa lusin nilai, tentu, itu mungkin baik-baik saja. Beberapa ribu? Ya, mungkin tidak terlalu banyak. YMMV, batasan dan pengecualian berlaku, tidak ada jaminan tersurat maupun tersirat.
Tapi itu berhasil.
sumber
Hanya untuk kelengkapan: Selama himpunan nilai tidak terlalu besar, Anda juga bisa membuat pernyataan seperti
yang kemudian bisa Anda lewati untuk mempersiapkan (), dan kemudian menggunakan setXXX () dalam satu lingkaran untuk mengatur semua nilai. Ini kelihatannya menjijikkan, tetapi banyak sistem komersial "besar" secara rutin melakukan hal semacam ini sampai mencapai batas spesifik DB, seperti 32 KB (menurut saya) untuk pernyataan di Oracle.
Tentu saja Anda perlu memastikan bahwa set tidak akan pernah terlalu besar, atau melakukan jebakan kesalahan jika itu terjadi.
sumber
Mengikuti ide Adam. Buat pernyataan yang disiapkan Anda pilih my_column dari my_table di mana search_column di (#) Buat sebuah String x dan isi dengan sejumlah "?,?,?" tergantung pada daftar nilai Anda Kemudian ubah saja # dalam kueri untuk String x baru Anda
sumber
Hasilkan string kueri di PreparedStatement untuk memiliki sejumlah? Yang cocok dengan jumlah item dalam daftar Anda. Ini sebuah contoh:
sumber
StringBuilder
. Tapi tidak dengan cara Anda berpikir. DecompilinggenerateQsForIn
Anda dapat melihat bahwa per loop iterasi dua baruStringBuilder
dialokasikan dantoString
disebut pada setiap. TheStringBuilder
optimasi hanya menangkap hal-hal seperti"x" + i+ "y" + j
tetapi tidak melampaui satu ekspresi.ps.setObject(1,items)
alih-alih mengulang daftar dan kemudian mengaturparamteres
?Ada beberapa pendekatan alternatif yang bisa kita gunakan untuk klausa IN di PreparedStatement.
Gunakan NULL dalam kueri PreparedStatement - Performa optimal, bekerja sangat baik bila Anda tahu batas argumen klausa IN. Jika tidak ada batasan, maka Anda bisa menjalankan kueri dalam batch. Cuplikan kode contoh adalah;
Anda dapat memeriksa lebih detail tentang pendekatan alternatif ini di sini .
sumber
Untuk beberapa situasi, regexp mungkin membantu. Berikut adalah contoh yang telah saya periksa di Oracle, dan itu berfungsi.
Tapi ada beberapa kelemahannya:
sumber
Setelah memeriksa berbagai solusi di forum yang berbeda dan tidak menemukan solusi yang baik, saya merasa hack di bawah ini yang saya buat, adalah yang termudah untuk diikuti dan kode:
Contoh: Misalkan Anda memiliki beberapa parameter untuk lulus dalam klausa 'IN'. Cukup masukkan String boneka di dalam klausa 'IN', katakan, "PARAM" menunjukkan daftar parameter yang akan datang di tempat String boneka ini.
Anda dapat mengumpulkan semua parameter menjadi variabel String tunggal dalam kode Java Anda. Hal ini dapat dilakukan sebagai berikut:
Anda dapat menambahkan semua parameter Anda dipisahkan dengan koma menjadi variabel String tunggal, 'param1', dalam kasus kami.
Setelah mengumpulkan semua parameter menjadi String tunggal, Anda bisa mengganti teks dummy dalam kueri Anda, yaitu, "PARAM" dalam kasus ini, dengan parameter String, yaitu, param1. Inilah yang perlu Anda lakukan:
Anda sekarang dapat menjalankan kueri Anda menggunakan metode executeQuery (). Pastikan Anda tidak memiliki kata "PARAM" di kueri Anda di mana pun. Anda dapat menggunakan kombinasi karakter dan huruf khusus alih-alih kata "PARAM" untuk memastikan bahwa tidak ada kemungkinan kata seperti itu masuk dalam kueri. Semoga Anda mendapatkan solusinya.
Catatan: Meskipun ini bukan kueri yang disiapkan, ia melakukan pekerjaan yang saya ingin kode saya lakukan.
sumber
Hanya untuk kelengkapan dan karena saya tidak melihat orang lain menyarankannya:
Sebelum menerapkan salah satu saran rumit di atas, pertimbangkan apakah injeksi SQL memang merupakan masalah dalam skenario Anda.
Dalam banyak kasus, nilai yang diberikan kepada IN (...) adalah daftar id yang telah dihasilkan sedemikian rupa sehingga Anda dapat memastikan bahwa tidak ada injeksi yang mungkin ... (mis. Hasil pemilihan some_id sebelumnya dari some_table di mana some_condition.)
Jika itu masalahnya Anda mungkin hanya menggabungkan nilai ini dan tidak menggunakan layanan atau pernyataan yang disiapkan untuk itu atau menggunakannya untuk parameter lain dari permintaan ini.
sumber
PreparedStatement tidak menyediakan cara yang baik untuk berurusan dengan klausa SQL IN. Per http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Anda tidak dapat menggantikan hal-hal yang dimaksudkan untuk menjadi bagian dari pernyataan SQL. Ini diperlukan karena jika SQL itu sendiri dapat berubah, Driver tidak dapat mengkompilasi pernyataan. Ini juga memiliki efek samping yang bagus untuk mencegah serangan injeksi SQL. " Saya akhirnya menggunakan pendekatan berikut:
sumber
SetArray adalah solusi terbaik tetapi tidak tersedia untuk banyak driver lama. Solusi berikut ini dapat digunakan di java8
Solusi ini lebih baik daripada solusi loop jelek lainnya di mana string kueri dibangun oleh iterasi manual
sumber
Ini bekerja untuk saya (psuedocode):
spesifik mengikat:
sumber
Contoh saya untuk database SQLite dan Oracle.
For loop pertama adalah untuk Membuat Objek PreparedStatement.
Untuk loop kedua adalah untuk Memasok Nilai untuk Parameter PreparedStatement.
sumber
Solusi saya (JavaScript)
SearchTerms
adalah array yang berisi input / kunci / bidang Anda, dllsumber