Parameterkan klausa SQL IN

1041

Bagaimana cara parameterisasi kueri yang berisi IN klausa dengan sejumlah variabel argumen, seperti ini?

SELECT * FROM Tags 
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC

Dalam kueri ini, jumlah argumen bisa berkisar dari 1 hingga 5.

Saya lebih suka tidak menggunakan prosedur tersimpan khusus untuk ini (atau XML), tetapi jika ada beberapa cara elegan khusus untuk SQL Server 2008 , saya terbuka untuk itu.

Jeff Atwood
sumber

Jawaban:

315

Inilah teknik cepat dan kotor yang saya gunakan:

SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'

Jadi, inilah kode C #:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + @tags + '|' like '%|' + Name + '|%'";

using (SqlCommand cmd = new SqlCommand(cmdText)) {
   cmd.Parameters.AddWithValue("@tags", string.Join("|", tags);
}

Dua peringatan:

  • Performanya mengerikan. LIKE "%...%"kueri tidak diindeks.
  • Pastikan Anda tidak memiliki |tag apa pun , kosong, atau nol atau ini tidak akan berfungsi

Ada cara lain untuk mencapai hal ini yang beberapa orang mungkin anggap lebih bersih, jadi silakan terus membaca.

Joel Spolsky
sumber
119
Itu akan sangat lambat
Matt Rogish
13
Ya, ini adalah pemindaian tabel. Besar untuk 10 baris, buruk untuk 100.000.
Will Hartung
17
Pastikan Anda menguji pada tag yang memiliki pipa di dalamnya.
Joel Coehoorn
17
Ini bahkan tidak menjawab pertanyaan. Memang, mudah untuk melihat di mana menambahkan parameter, tetapi bagaimana Anda bisa menerima ini solusi jika bahkan tidak repot-repot untuk parameterisasi kueri? Itu hanya terlihat lebih sederhana daripada @ Mark Brackett's karena tidak parameter.
tvanfosson
21
Bagaimana jika tag Anda adalah 'ruby | rails'. Itu akan cocok, yang akan salah. Saat Anda meluncurkan solusi seperti itu, Anda perlu memastikan bahwa tag tidak mengandung pipa, atau menyaringnya secara eksplisit: pilih * dari Tag di mana '| ruby ​​| rails | scruffy | rubyonrails |' seperti '% |' + Nama + '|%' DAN nama tidak seperti '%!%'
AK
729

Anda dapat parameter setiap nilai, jadi sesuatu seperti:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";

string[] paramNames = tags.Select(
    (s, i) => "@tag" + i.ToString()
).ToArray();

string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
    for(int i = 0; i < paramNames.Length; i++) {
       cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
    }
}

Yang akan memberi Anda:

cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (@tag0, @tag1, @tag2, @tag3)"
cmd.Parameters["@tag0"] = "ruby"
cmd.Parameters["@tag1"] = "rails"
cmd.Parameters["@tag2"] = "scruffy"
cmd.Parameters["@tag3"] = "rubyonrails"

Tidak, ini tidak terbuka untuk injeksi SQL . Satu-satunya teks yang disuntikkan ke CommandText tidak didasarkan pada input pengguna. Ini semata-mata didasarkan pada awalan "@ tag" hardcoded, dan indeks array. Indeks akan selalu berupa bilangan bulat, bukan hasil pengguna, dan aman.

Nilai yang dimasukkan pengguna masih dimasukkan ke dalam parameter, sehingga tidak ada kerentanan di sana.

Edit:

Mengesampingkan masalah injeksi, berhati-hatilah untuk mencatat bahwa membangun teks perintah untuk mengakomodasi sejumlah variabel parameter (seperti di atas) menghambat kemampuan SQL server untuk mengambil keuntungan dari query yang di-cache. Hasil akhirnya adalah bahwa Anda hampir pasti kehilangan nilai menggunakan parameter di tempat pertama (bukan hanya memasukkan string predikat ke dalam SQL itu sendiri).

Bukan berarti paket kuota yang di-cache tidak berharga, tetapi IMO kueri ini tidak cukup rumit untuk melihat banyak manfaat darinya. Meskipun biaya kompilasi mungkin mendekati (atau bahkan melebihi) biaya eksekusi, Anda masih berbicara milidetik.

Jika Anda memiliki RAM yang cukup, saya berharap SQL Server mungkin akan men-cache rencana untuk jumlah parameter yang umum juga. Saya kira Anda selalu dapat menambahkan lima parameter, dan membiarkan tag yang tidak ditentukan menjadi NULL - rencana kueri harus sama, tetapi tampaknya cukup jelek bagi saya dan saya tidak yakin bahwa itu layak untuk optimasi mikro (walaupun, pada Stack Overflow - mungkin sangat berharga).

Selain itu, SQL Server 7 dan yang lebih baru akan auto-parameterize query , jadi menggunakan parameter tidak benar-benar diperlukan dari sudut pandang kinerja - namun, penting dari sudut pandang keamanan - terutama dengan data yang dimasukkan pengguna seperti ini.

Mark Brackett
sumber
2
Pada dasarnya sama dengan jawaban saya untuk pertanyaan "terkait" dan jelas solusi terbaik karena konstruktif dan efisien daripada interpretatif (jauh lebih sulit).
tvanfosson
49
Ini adalah bagaimana LINQ to SQL melakukannya, BTW
Mark Cidade
3
@ Murni: Inti dari semua ini adalah untuk menghindari SQL Injection, yang Anda akan rentan jika Anda menggunakan SQL dinamis.
Ray
4
@ Dewa Data - Ya, saya kira jika Anda membutuhkan lebih dari 2.100 tag, Anda akan membutuhkan solusi yang berbeda. Tetapi Basarb's hanya bisa mencapai 2100 jika panjang tag rata-rata adalah <3 karakter (karena Anda juga membutuhkan pembatas). msdn.microsoft.com/en-us/library/ms143432.aspx
Mark Brackett
2
@bonCodigo - nilai yang Anda pilih ada dalam array; Anda hanya perlu mengulang array dan menambahkan parameter (diakhiri dengan indeks) untuk masing-masing.
Mark Brackett
249

Untuk SQL Server 2008, Anda dapat menggunakan parameter tabel bernilai . Ini sedikit bekerja, tetapi ini bisa dibilang lebih bersih daripada metode saya yang lain .

Pertama, Anda harus membuat tipe

CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )

Kemudian, kode ADO.NET Anda terlihat seperti ini:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN @tagNames as P ON Tags.Name = P.Name";

// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("@tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["@tagNames"].TypeName = "dbo.TagNamesTableType";

// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
    if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
    var firstRecord = values.First();
    var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
    return values.Select(v => 
    {
       var r = new SqlDataRecord(metadata);
       r.SetValues(v);
       return r;
    });
}
Mark Brackett
sumber
41
kami menguji ini dan parameter tabel bernilai DOG lambat. Secara harfiah lebih cepat untuk mengeksekusi 5 kueri daripada melakukan satu TVP.
Jeff Atwood
4
@JeffAtwood - Sudahkah Anda mencoba mengubah susunan kueri menjadi sesuatu seperti SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);? Secara teori, ini seharusnya menjadi pendekatan tercepat. Anda dapat menggunakan indeks yang relevan (misalnya indeks pada nama tag yang INCLUDEdianggap ideal), dan SQL Server harus melakukan beberapa upaya untuk mengambil semua tag dan jumlah mereka. Seperti apa rencananya?
Nick Chammas
9
Saya juga telah menguji ini dan ini CEPAT SEPERTI LIGHTNING (dibandingkan dengan membangun string IN besar). Saya punya beberapa masalah pengaturan parameter karena saya selalu mendapatkan "Gagal mengonversi nilai parameter dari Int32 [] ke IEnumerable`1.". Pokoknya, selesaikan itu dan inilah contoh yang saya buat pastebin.com/qHP05CXc
Fredrik Johansson
6
@FredrikJohansson - Dari 130 upvote, Anda mungkin satu-satunya yang mencoba menjalankan ini! Saya membuat kesalahan dengan membaca dokumen, dan Anda benar-benar membutuhkan IEnumerable <SqlDataRecord>, bukan sembarang IEnumerable. Kode telah diperbarui.
Mark Brackett
3
@MarkBrackett Hebat dengan pembaruan! Sebenarnya kode ini benar-benar menyelamatkan saya sejak hari ini karena saya menanyai indeks pencarian Lucene dan kadang-kadang mengembalikan lebih dari 50.000 atau lebih yang perlu diperiksa dua kali lipat terhadap SQL server - Jadi saya membuat array int [] (dokumen / Kunci SQL) dan kemudian kode di atas masuk. Seluruh OP sekarang membutuhkan waktu kurang dari 200 ms :)
Fredrik Johansson
188

Pertanyaan aslinya adalah "Bagaimana cara parameterisasi kueri ..."

Izinkan saya menyatakan di sini, bahwa ini bukan jawaban untuk pertanyaan awal. Sudah ada beberapa demonstrasi tentang hal itu di jawaban yang baik lainnya.

Dengan mengatakan itu, silakan dan tandai jawaban ini, turunkan suaranya, tandai sebagai bukan jawaban ... lakukan apa pun yang menurut Anda benar.

Lihat jawaban dari Mark Brackett untuk jawaban yang lebih disukai yang saya (dan 231 lainnya) angkat. Pendekatan yang diberikan dalam jawabannya memungkinkan 1) untuk penggunaan variabel terikat yang efektif, dan 2) untuk predikat yang lebih besar.

Jawaban yang dipilih

Yang ingin saya sampaikan di sini adalah pendekatan yang diberikan dalam jawaban Joel Spolsky, jawaban "terpilih" sebagai jawaban yang tepat.

Pendekatan Joel Spolsky cerdas. Dan itu bekerja dengan wajar, itu akan menunjukkan perilaku yang dapat diprediksi dan kinerja yang dapat diprediksi, diberi nilai "normal", dan dengan kasus tepi normatif, seperti NULL dan string kosong. Dan itu mungkin cukup untuk aplikasi tertentu.

Tetapi dalam hal menggeneralisasikan pendekatan ini, mari kita juga mempertimbangkan kasus sudut yang lebih jelas, seperti ketika Namekolom berisi karakter wildcard (seperti yang dikenali oleh predikat LIKE). Karakter wildcard yang saya lihat paling umum digunakan adalah %(tanda persen.). Jadi mari kita hadapi itu di sini sekarang, dan kemudian lanjutkan ke kasus lain.

Beberapa masalah dengan% karakter

Pertimbangkan nilai Nama 'pe%ter'. (Untuk contoh di sini, saya menggunakan nilai string literal sebagai ganti nama kolom.) Baris dengan nilai Nama `'pe% ter' akan dikembalikan oleh kueri bentuk:

select ...
 where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'

Tapi baris yang sama itu tidak akan dikembalikan jika urutan istilah pencarian dibalik:

select ...
 where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'

Perilaku yang kita amati agak aneh. Mengubah urutan istilah pencarian dalam daftar akan mengubah set hasil.

Hampir tidak perlu dikatakan bahwa kita mungkin tidak ingin pe%termencocokkan selai kacang, tidak peduli seberapa besar dia menyukainya.

Kasus sudut tidak jelas

(Ya, saya akan setuju bahwa ini adalah kasus yang tidak jelas. Mungkin yang tidak mungkin diuji. Kami tidak akan mengharapkan wildcard dalam nilai kolom. Kami dapat mengasumsikan bahwa aplikasi mencegah nilai disimpan dari nilai tersebut. Tetapi dalam pengalaman saya, saya jarang melihat kendala basis data yang secara khusus melarang karakter atau pola yang akan dianggap wildcard di sisi kanan LIKEoperator pembanding.

Menambal lubang

Salah satu pendekatan untuk menambal lubang ini adalah untuk melarikan diri dari %karakter wildcard. (Untuk siapa pun yang tidak terbiasa dengan klausa melarikan diri pada operator, berikut ini tautan ke dokumentasi SQL Server .

select ...
 where '|peanut|butter|'
  like '%|' + 'pe\%ter' + '|%' escape '\'

Sekarang kita dapat mencocokkan% literal. Tentu saja, ketika kita memiliki nama kolom, kita harus keluar secara dinamis dari wildcard. Kita dapat menggunakan REPLACEfungsi untuk menemukan kemunculan %karakter dan menyisipkan karakter garis miring terbalik di depan masing-masing karakter, seperti ini:

select ...
 where '|pe%ter|'
  like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'

Sehingga memecahkan masalah dengan% wildcard. Hampir.

Escape the escape

Kami menyadari bahwa solusi kami telah menimbulkan masalah lain. Karakter melarikan diri. Kita melihat bahwa kita juga perlu melarikan diri dari setiap kejadian karakter pelarian itu sendiri. Kali ini, kami menggunakan! sebagai karakter pelarian:

select ...
 where '|pe%t!r|'
  like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'

Garis bawah juga

Sekarang kita berada di roll, kita dapat menambahkan REPLACEpegangan lain wildcard garis bawah. Dan hanya untuk bersenang-senang, kali ini, kami akan menggunakan $ sebagai karakter pelarian.

select ...
 where '|p_%t!r|'
  like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'

Saya lebih suka pendekatan ini untuk melarikan diri karena ia bekerja di Oracle dan MySQL serta SQL Server. (Saya biasanya menggunakan \ backslash sebagai karakter pelarian, karena itulah karakter yang kami gunakan dalam ekspresi reguler. Tapi mengapa harus dibatasi oleh konvensi!

Kurung sial itu

SQL Server juga memungkinkan karakter wildcard diperlakukan sebagai literal dengan melampirkannya dalam tanda kurung []. Jadi kita belum selesai memperbaiki, setidaknya untuk SQL Server. Karena pasangan tanda kurung memiliki arti khusus, kita juga perlu menghindarinya. Jika kita berhasil keluar dari kurung, maka setidaknya kita tidak perlu repot dengan tanda hubung -dan karat ^di dalam kurung. Dan kita bisa pergi% dan _karakter di dalam tanda kurung lolos, karena pada dasarnya kita akan menonaktifkan arti khusus tanda kurung.

Menemukan pasangan kurung yang cocok seharusnya tidak sulit. Ini sedikit lebih sulit daripada menangani kejadian singleton% dan _. (Perhatikan bahwa tidak cukup untuk hanya melarikan diri dari semua kejadian kurung, karena braket tunggal dianggap literal, dan tidak perlu diloloskan. Logikanya menjadi sedikit lebih kabur daripada yang bisa saya tangani tanpa menjalankan lebih banyak kasus uji .)

Ekspresi sebaris menjadi berantakan

Ungkapan inline dalam SQL semakin panjang dan jelek. Kita mungkin dapat membuatnya bekerja, tetapi surga membantu jiwa miskin yang datang dan harus menguraikannya. Sebanyak penggemar saya untuk ekspresi inline, saya cenderung tidak menggunakannya di sini, terutama karena saya tidak ingin harus meninggalkan komentar menjelaskan alasan kekacauan, dan meminta maaf untuk itu.

Fungsi mana?

Oke, jadi, jika kita tidak mengatasinya sebagai ekspresi inline dalam SQL, alternatif terdekat yang kita miliki adalah fungsi yang ditentukan pengguna. Dan kita tahu bahwa tidak akan mempercepat apa pun (kecuali kita dapat menentukan indeks di atasnya, seperti kita bisa dengan Oracle.) Jika kita harus membuat fungsi, kita mungkin lebih baik melakukannya dalam kode yang memanggil SQL pernyataan.

Dan fungsi itu mungkin memiliki beberapa perbedaan dalam perilaku, tergantung pada DBMS dan versi. (Teriakan untuk semua pengembang Java Anda yang ingin sekali menggunakan mesin basis data secara bergantian.)

Pengetahuan domain

Kami mungkin memiliki pengetahuan khusus tentang domain untuk kolom, (yaitu, kumpulan nilai yang diizinkan yang diberlakukan untuk kolom tersebut. Kita mungkin mengetahui apriori bahwa nilai yang disimpan dalam kolom tidak akan pernah mengandung tanda persen, garis bawah, atau braket dalam hal ini, kami hanya menyertakan komentar cepat bahwa kasus-kasus tersebut dibahas.

Nilai-nilai yang disimpan dalam kolom memungkinkan% atau _ karakter, tetapi kendala mungkin mengharuskan nilai-nilai itu untuk melarikan diri, mungkin menggunakan karakter yang ditentukan, sehingga nilai-nilai tersebut SEPERTI perbandingan "aman". Sekali lagi, komentar singkat tentang set nilai yang diperbolehkan, dan khususnya karakter mana yang digunakan sebagai karakter pelarian, dan ikuti pendekatan Joel Spolsky.

Tetapi, tanpa pengetahuan khusus dan jaminan, penting bagi kami untuk setidaknya mempertimbangkan menangani kasus sudut yang tidak jelas itu, dan mempertimbangkan apakah perilaku tersebut masuk akal dan "sesuai spesifikasi".


Masalah-masalah lain direkapitulasi

Saya percaya orang lain telah cukup menunjukkan beberapa bidang lain yang dianggap umum:

  • Injeksi SQL (mengambil apa yang kelihatannya merupakan informasi yang disediakan pengguna, dan memasukkannya dalam teks SQL alih-alih memasoknya melalui variabel bind. Menggunakan variabel bind tidak diperlukan, itu hanya satu pendekatan yang mudah untuk menggagalkan dengan injeksi SQL. Ada yang lain cara untuk menghadapinya:

  • rencana pengoptimalisasi menggunakan pemindaian indeks daripada pencarian indeks, kemungkinan kebutuhan untuk ekspresi atau fungsi untuk keluar dari wildcard (indeks kemungkinan pada ekspresi atau fungsi)

  • menggunakan nilai literal sebagai pengganti variabel pengikat berdampak skalabilitas


Kesimpulan

Saya suka pendekatan Joel Spolsky. Itu pintar. Dan itu berhasil.

Tapi begitu saya melihatnya, saya langsung melihat potensi masalah dengan itu, dan bukan sifat saya untuk membiarkannya meluncur. Saya tidak bermaksud kritis terhadap upaya orang lain. Saya tahu banyak pengembang mengambil pekerjaan mereka dengan sangat pribadi, karena mereka berinvestasi begitu banyak ke dalamnya dan mereka sangat peduli tentang itu. Jadi tolong mengerti, ini bukan serangan pribadi. Apa yang saya identifikasi di sini adalah jenis masalah yang muncul dalam produksi daripada pengujian.

Ya, saya sudah jauh dari pertanyaan awal. Tetapi di mana lagi harus meninggalkan catatan tentang apa yang saya anggap sebagai masalah penting dengan jawaban "terpilih" untuk sebuah pertanyaan?

spencer7593
sumber
dapatkah Anda memberi tahu kami jika Anda menggunakan atau menyukai permintaan parameterized? dalam kasus khusus ini apakah benar untuk melompati aturan 'menggunakan parameterized querys' dan membersihkan dengan bahasa asli? TERIMA KASIH
Luis Siquot
2
@Luis: ya, saya lebih suka menggunakan variabel bind dalam pernyataan SQL, dan hanya akan menghindari variabel bind saat menggunakannya menyebabkan masalah kinerja. Pola normatif saya untuk masalah awal adalah secara dinamis membuat pernyataan SQL dengan jumlah placeholder yang diperlukan dalam daftar IN, dan kemudian mengikat setiap nilai ke salah satu placeholder. Lihat jawaban dari Mark Brackett, yang merupakan jawaban yang saya (dan 231 lainnya) angkat.
spencer7593
133

Anda dapat melewatkan parameter sebagai string

Jadi kamu punya talinya

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

select * from Tags 
where Name in (SELECT item from fnSplit(@tags, ‘|’))
order by Count desc

Maka yang harus Anda lakukan adalah meneruskan string sebagai 1 parameter.

Berikut adalah fungsi split yang saya gunakan.

CREATE FUNCTION [dbo].[fnSplit](
    @sInputList VARCHAR(8000) -- List of delimited items
  , @sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS @List TABLE (item VARCHAR(8000))

BEGIN
DECLARE @sItem VARCHAR(8000)
WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
 BEGIN
 SELECT
  @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))),
  @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

 IF LEN(@sItem) > 0
  INSERT INTO @List SELECT @sItem
 END

IF LEN(@sInputList) > 0
 INSERT INTO @List SELECT @sInputList -- Put the last item in
RETURN
END
David Basarab
sumber
2
Anda juga dapat bergabung ke fungsi tabel dengan pendekatan ini.
Michael Haren
Saya menggunakan solusi yang mirip dengan ini di Oracle. Itu tidak harus diurai kembali seperti beberapa solusi lainnya.
Leigh Riffel
9
Ini adalah pendekatan basis data murni yang membutuhkan pekerjaan lain dalam kode di luar basis data.
David Basarab
Apakah ini ke pemindaian tabel atau dapatkah memanfaatkan indeks, dll?
Pure.Krome
lebih baik menggunakan CROSS BERLAKU terhadap fungsi tabel SQL (setidaknya pada tahun 2005 dan seterusnya), yang pada dasarnya bergabung dengan tabel yang dikembalikan
adolf bawang putih
66

Saya mendengar Jeff / Joel membicarakan hal ini di podcast hari ini ( episode 34 , 2008-12-16 (MP3, 31 MB), 1 jam 03 menit 38 detik - 1 jam 06 menit 45 detik), dan saya pikir saya ingat Stack Overflow menggunakan LINQ ke SQL , tapi mungkin itu dibuang. Inilah hal yang sama di LINQ ke SQL.

var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };

var results = from tag in Tags
              where inValues.Contains(tag.Name)
              select tag;

Itu dia. Dan, ya, LINQ sudah terlihat cukup mundur, tetapi Containsklausa itu tampak lebih mundur bagi saya. Ketika saya harus melakukan permintaan serupa untuk sebuah proyek di tempat kerja, saya secara alami mencoba melakukan ini dengan cara yang salah dengan melakukan gabungan antara array lokal dan tabel SQL Server, dengan mencari penerjemah LINQ ke SQL akan cukup pintar untuk menangani terjemahan entah bagaimana. Itu tidak, tapi itu memberikan pesan kesalahan yang deskriptif dan mengarahkan saya ke arah menggunakan Contains .

Bagaimanapun, jika Anda menjalankan ini di LINQPad yang sangat disarankan , dan menjalankan kueri ini, Anda dapat melihat SQL aktual yang dihasilkan oleh penyedia LINQ SQL. Ini akan menunjukkan kepada Anda masing-masing nilai yang mendapatkan parameter menjadi INklausa.

Peter Meyer
sumber
50

Jika Anda menelepon dari .NET, Anda bisa menggunakan Dapper dot net :

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(@"
select * from Tags 
where Name in @names
order by Count desc", new {names});

Di sini Dapper yang berpikir, jadi Anda tidak perlu melakukannya. Hal serupa mungkin terjadi dengan LINQ ke SQL , tentu saja:

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
           where names.Contains(tag.Name)
           orderby tag.Count descending
           select tag;
Marc Gravell
sumber
11
yang kebetulan adalah apa yang kami gunakan pada halaman ini, untuk pertanyaan aktual yang diajukan (necis) i.stack.imgur.com/RBAjL.png
Sam Saffron
3
Perhatikan bahwa necis sekarang juga mendukung Table Valued Parameters sebagai warga negara kelas satu
Marc Gravell
Ini jatuh jika nama panjang
cs0815
29

Ini mungkin cara setengah jahat melakukannya, saya pernah menggunakannya, agak efektif.

Tergantung pada tujuan Anda, itu mungkin berguna.

  1. Buat tabel temp dengan satu kolom.
  2. INSERT setiap nilai pencarian ke dalam kolom itu.
  3. Alih-alih menggunakan IN, Anda kemudian bisa menggunakan JOINaturan standar Anda . (Fleksibilitas ++)

Ini memiliki sedikit fleksibilitas tambahan dalam apa yang dapat Anda lakukan, tetapi lebih cocok untuk situasi di mana Anda memiliki tabel besar untuk query, dengan pengindeksan yang baik, dan Anda ingin menggunakan daftar parametrized lebih dari sekali. Menghemat pelaksanaannya dua kali dan sanitasi dilakukan secara manual.

Saya tidak pernah sempat membuat profil seberapa cepat itu, tetapi dalam situasi saya diperlukan.

Kent Fredric
sumber
Ini sama sekali tidak jahat! Bahkan lebih, itu IMHO cara yang sangat bersih. Dan jika Anda melihat ke dalam rencana eksekusi, Anda melihat bahwa itu sama seperti klausa IN. Alih-alih tabel temp, Anda juga bisa membuat tabel tetap dengan indeks, tempat Anda menyimpan parameter bersama dengan SESSIONID.
SQL Police
27

Dalam SQL Server 2016+Anda bisa menggunakan STRING_SPLITfungsi:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT * 
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(@names, ','))
ORDER BY [Count] DESC;

atau:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(@names,',')
  ON t.Name = [value]
ORDER BY [Count] DESC;

LiveDemo

The jawaban yang diterima tentu saja akan bekerja dan ini adalah salah satu cara untuk pergi, tapi itu adalah anti-pola.

E. Temukan baris berdasarkan daftar nilai

Ini adalah pengganti untuk anti-pola umum seperti membuat string SQL dinamis di lapisan aplikasi atau Transact-SQL, atau dengan menggunakan operator LIKE:

SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';

Adendum :

Untuk meningkatkan STRING_SPLITestimasi baris fungsi tabel, adalah ide yang bagus untuk mematerialisasikan nilai yang dibagi sebagai variabel tabel / tabel sementara:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';

CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(@names, ',') s;

SELECT *
FROM Tags tg
JOIN #t t
  ON t.val = tg.TagName
ORDER BY [Count] DESC;

SEDE - Demo Langsung

Terkait: Cara Melewati Daftar Nilai Ke Prosedur yang Disimpan


Pertanyaan awal memiliki persyaratan SQL Server 2008. Karena pertanyaan ini sering digunakan sebagai duplikat, saya menambahkan jawaban ini sebagai referensi.

Lukasz Szozda
sumber
1
Saya belum pernah menguji ini, tetapi saya merasa ini adalah solusi 2016+ terbersih. Saya masih ingin bisa melewati serangkaian int, tetapi sampai saat itu ...
Daniel
24

Kami memiliki fungsi yang membuat variabel tabel yang dapat Anda ikuti:

ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list  AS VARCHAR(8000),
                                           @delim AS VARCHAR(10))
RETURNS @listTable TABLE(
  Position INT,
  Value    VARCHAR(8000))
AS
  BEGIN
      DECLARE @myPos INT

      SET @myPos = 1

      WHILE Charindex(@delim, @list) > 0
        BEGIN
            INSERT INTO @listTable
                        (Position,Value)
            VALUES     (@myPos,LEFT(@list, Charindex(@delim, @list) - 1))

            SET @myPos = @myPos + 1

            IF Charindex(@delim, @list) = Len(@list)
              INSERT INTO @listTable
                          (Position,Value)
              VALUES     (@myPos,'')

            SET @list = RIGHT(@list, Len(@list) - Charindex(@delim, @list))
        END

      IF Len(@list) > 0
        INSERT INTO @listTable
                    (Position,Value)
        VALUES     (@myPos,@list)

      RETURN
  END 

Begitu:

@Name varchar(8000) = null // parameter for search values    

select * from Tags 
where Name in (SELECT value From fn_sqllist_to_table(@Name,',')))
order by Count desc
David Robbins
sumber
20

Ini kotor, tetapi jika Anda dijamin memiliki setidaknya satu, Anda bisa melakukannya:

SELECT ...
       ...
 WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )

Memiliki IN ('tag1', 'tag2', 'tag1', 'tag1', 'tag1') akan dengan mudah dioptimalkan oleh SQL Server. Plus, Anda mendapatkan pencarian indeks langsung

Matt Rogish
sumber
1
Parameter opsional dengan Null memeriksa kinerja rusak, karena pengoptimal membutuhkan jumlah parameter yang digunakan untuk membuat kueri yang efisien. Permintaan untuk 5 parameter mungkin memerlukan rencana permintaan yang berbeda dari yang untuk 500 parameter.
Erik Hart
18

Menurut pendapat saya, sumber terbaik untuk mengatasi masalah ini, adalah apa yang telah diposting di situs ini:

Syscomments. Dinakar Nethi

CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))
RETURNS @T Table (col1 varchar(50))
AS 
BEGIN
 --DECLARE @T Table (col1 varchar(50))  
 -- @Array is the array we wish to parse
 -- @Separator is the separator charactor such as a comma
 DECLARE @separator_position INT -- This is used to locate each separator character
 DECLARE @array_value VARCHAR(1000) -- this holds each array value as it is returned
 -- For my loop to work I need an extra separator at the end. I always look to the
 -- left of the separator character for each array value

 SET @array = @array + @separator

 -- Loop through the string searching for separtor characters
 WHILE PATINDEX('%' + @separator + '%', @array) <> 0 
 BEGIN
    -- patindex matches the a pattern against a string
    SELECT @separator_position = PATINDEX('%' + @separator + '%',@array)
    SELECT @array_value = LEFT(@array, @separator_position - 1)
    -- This is where you process the values passed.
    INSERT into @T VALUES (@array_value)    
    -- Replace this select statement with your processing
    -- @array_value holds the value of this element of the array
    -- This replaces what we just processed with and empty string
    SELECT @array = STUFF(@array, 1, @separator_position, '')
 END
 RETURN 
END

Menggunakan:

SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')

KREDIT UNTUK: Dinakar Nethi

Paulo Henrique
sumber
Jawaban yang bagus, bersih dan modular, eksekusi sangat cepat kecuali untuk CSV awal yang menguraikan tabel (satu kali, sejumlah kecil elemen). Meskipun bisa menggunakan charindex () yang lebih sederhana / lebih cepat daripada patindex ()? Charindex () juga memungkinkan argumen 'start_location' yang mungkin dapat menghindari memotong string input setiap iter? Untuk menjawab pertanyaan awal bisa langsung bergabung dengan hasil fungsi.
crokusek
18

Saya akan melewati parameter tipe tabel (karena ini SQL Server 2008 ), dan melakukan a where exists, atau inner join. Anda juga dapat menggunakan XML, menggunakan sp_xml_preparedocument, dan kemudian bahkan mengindeks tabel sementara itu.

eulerfx
sumber
Jawaban Ph.E memiliki contoh tabel temp bangunan (dari csv).
crokusek
12

Cara yang tepat IMHO adalah menyimpan daftar dalam string karakter (panjangnya dibatasi oleh apa yang didukung DBMS); satu-satunya trik adalah bahwa (untuk menyederhanakan pemrosesan) saya memiliki pemisah (koma dalam contoh saya) di awal dan di akhir string. Idenya adalah untuk "menormalkan dengan cepat", mengubah daftar menjadi tabel satu kolom yang berisi satu baris per nilai. Ini memungkinkan Anda untuk berbalik

dalam (ct1, ct2, ct3 ... ctn)

menjadi sebuah

di (pilih ...)

atau (solusi saya mungkin lebih suka) bergabung secara teratur, jika Anda hanya menambahkan "berbeda" untuk menghindari masalah dengan nilai duplikat dalam daftar.

Sayangnya, teknik untuk mengiris string cukup spesifik untuk produk. Ini adalah versi SQL Server:

 with qry(n, names) as
       (select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
               substring(list.names, 2, len(list.names)) as names
        from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
        union all
        select (n - 1) as n,
               substring(names, 1 + charindex(',', names), len(names)) as names
        from qry
        where n > 1)
 select n, substring(names, 1, charindex(',', names) - 1) dwarf
 from qry;

Versi Oracle:

 select n, substr(name, 1, instr(name, ',') - 1) dwarf
 from (select n,
             substr(val, 1 + instr(val, ',', 1, n)) name
      from (select rownum as n,
                   list.val
            from  (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
                   from dual) list
            connect by level < length(list.val) -
                               length(replace(list.val, ',', ''))));

dan versi MySQL:

select pivot.n,
      substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
     union all
     select 2 as n
     union all
     select 3 as n
     union all
     select 4 as n
     union all
     select 5 as n
     union all
     select 6 as n
     union all
     select 7 as n
     union all
     select 8 as n
     union all
     select 9 as n
     union all
     select 10 as n) pivot,    (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n <  length(list.val) -
                   length(replace(list.val, ',', ''));

(Tentu saja, "pivot" harus mengembalikan baris sebanyak jumlah item maksimum yang dapat kita temukan dalam daftar)

Jeff Atwood
sumber
11

Jika Anda punya SQL Server 2008 atau lebih baru, saya akan menggunakan Table Valued Parameter .

Jika Anda cukup beruntung terjebak di SQL Server 2005 Anda bisa menambahkan fungsi CLR seperti ini,

[SqlFunction(
    DataAccessKind.None,
    IsDeterministic = true,
    SystemDataAccess = SystemDataAccessKind.None,
    IsPrecise = true,
    FillRowMethodName = "SplitFillRow",
    TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
    if (s.IsNull)
        return new string[0];

    return s.ToString().Split(seperator.Buffer);
}

public static void SplitFillRow(object row, out SqlString s)
{
    s = new SqlString(row.ToString());
}

Yang bisa Anda gunakan seperti ini,

declare @desiredTags nvarchar(MAX);
set @desiredTags = 'ruby,rails,scruffy,rubyonrails';

select * from Tags
where Name in [dbo].[Split] (',', @desiredTags)
order by Count desc
Jodrell
sumber
10

Saya pikir ini adalah kasus ketika permintaan statis bukan cara untuk pergi. Secara dinamis membangun daftar untuk klausa Anda, keluar dari tanda kutip tunggal, dan secara dinamis membangun SQL. Dalam hal ini Anda mungkin tidak akan melihat banyak perbedaan dengan metode apa pun karena daftar kecil, tetapi metode yang paling efisien adalah mengirim SQL persis seperti yang tertulis dalam posting Anda. Saya pikir itu kebiasaan yang baik untuk menulisnya dengan cara yang paling efisien, daripada melakukan apa yang membuat kode tercantik, atau menganggapnya sebagai praktik buruk untuk membangun SQL secara dinamis.

Saya telah melihat fungsi split membutuhkan waktu lebih lama untuk dieksekusi daripada query sendiri dalam banyak kasus di mana parameter menjadi besar. Prosedur tersimpan dengan parameter nilai tabel di SQL 2008 adalah satu-satunya pilihan lain yang akan saya pertimbangkan, meskipun ini mungkin akan lebih lambat dalam kasus Anda. TVP mungkin hanya akan lebih cepat untuk daftar besar jika Anda mencari pada kunci utama TVP, karena SQL akan membangun tabel sementara untuk daftar itu (jika daftar besar). Anda tidak akan tahu pasti kecuali Anda mengujinya.

Saya juga melihat prosedur tersimpan yang memiliki 500 parameter dengan nilai default nol, dan memiliki WHERE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500). Ini menyebabkan SQL untuk membangun tabel temp, melakukan pengurutan / berbeda, dan kemudian melakukan pemindaian tabel alih-alih pencarian indeks. Itu pada dasarnya adalah apa yang akan Anda lakukan dengan parameterisasi permintaan itu, meskipun pada skala yang cukup kecil sehingga tidak akan membuat perbedaan yang nyata. Saya sangat menyarankan agar NULL tidak ada dalam daftar IN Anda, seolah-olah itu akan berubah menjadi NOT IN itu tidak akan bertindak seperti yang dimaksudkan. Anda bisa secara dinamis membangun daftar parameter, tetapi satu-satunya hal yang jelas akan Anda dapatkan adalah bahwa objek akan lolos dari tanda kutip tunggal untuk Anda. Pendekatan itu juga sedikit lebih lambat pada ujung aplikasi karena objek harus mengurai kueri untuk menemukan parameter.

Penggunaan kembali rencana eksekusi untuk prosedur tersimpan atau kueri parameterisasi dapat memberi Anda keuntungan kinerja, tetapi itu akan mengunci Anda ke satu paket eksekusi yang ditentukan oleh kueri pertama yang dieksekusi. Itu mungkin kurang dari ideal untuk pertanyaan selanjutnya dalam banyak kasus. Dalam kasus Anda, penggunaan kembali rencana eksekusi mungkin akan menjadi nilai tambah, tetapi mungkin tidak membuat perbedaan sama sekali karena contohnya adalah permintaan yang sangat sederhana.

Catatan Cliffs:

Untuk kasus Anda, apa pun yang Anda lakukan, baik itu parameterisasi dengan jumlah item tetap dalam daftar (nol jika tidak digunakan), secara dinamis membangun kueri dengan atau tanpa parameter, atau menggunakan prosedur tersimpan dengan parameter tabel bernilai tidak akan membuat banyak perbedaan . Namun, rekomendasi umum saya adalah sebagai berikut:

Kasing / pertanyaan sederhana Anda dengan beberapa parameter:

SQL dinamis, mungkin dengan parameter jika pengujian menunjukkan kinerja yang lebih baik.

Kueri dengan rencana eksekusi yang dapat digunakan kembali, dipanggil beberapa kali hanya dengan mengubah parameter atau jika kueri rumit:

SQL dengan parameter dinamis.

Kueri dengan daftar besar:

Prosedur tersimpan dengan parameter nilai tabel. Jika daftar dapat bervariasi dengan menggunakan jumlah besar DENGAN DIREKOMENDASIKAN pada prosedur tersimpan, atau cukup gunakan SQL dinamis tanpa parameter untuk menghasilkan rencana eksekusi baru untuk setiap permintaan.

Scott
sumber
Apa yang Anda maksud dengan "prosedur tersimpan" di sini? Bisakah Anda memposting contoh?
struhtanov
9

Mungkin kita bisa menggunakan XML di sini:

    declare @x xml
    set @x='<items>
    <item myvalue="29790" />
    <item myvalue="31250" />
    </items>
    ';
    With CTE AS (
         SELECT 
            x.item.value('@myvalue[1]', 'decimal') AS myvalue
        FROM @x.nodes('//items/item') AS x(item) )

    select * from YourTable where tableColumnName in (select myvalue from cte)
MindLoggedOut
sumber
1
CTEdan @xdapat dihilangkan / digarisbawahi ke dalam subselect, jika dilakukan dengan sangat hati-hati, seperti yang ditunjukkan dalam artikel ini .
robert4
9

Saya akan mendekati ini secara default dengan melewatkan fungsi bernilai tabel (yang mengembalikan tabel dari string) ke kondisi IN.

Ini adalah kode untuk UDF (saya mendapatkannya dari Stack Overflow di suatu tempat, saya tidak dapat menemukan sumbernya sekarang)

CREATE FUNCTION [dbo].[Split] (@sep char(1), @s varchar(8000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT 
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )

Setelah Anda mendapatkan ini, kode Anda akan sesederhana ini:

select * from Tags 
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc

Kecuali jika Anda memiliki string yang sangat panjang, ini akan bekerja dengan baik dengan indeks tabel.

Jika perlu Anda bisa memasukkannya ke tabel temp, indekskan, kemudian jalankan gabungan ...

Eli Ekstein
sumber
8

Solusi lain yang mungkin adalah alih-alih meneruskan sejumlah variabel argumen ke prosedur tersimpan, meneruskan string tunggal yang berisi nama-nama yang Anda cari, tetapi membuatnya unik dengan mengelilingi mereka dengan '<>'. Kemudian gunakan PATINDEX untuk menemukan nama-nama:

SELECT * 
FROM Tags 
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0
ArtOfCoding
sumber
8

Gunakan prosedur tersimpan berikut ini. Ini menggunakan fungsi pemisahan kustom, yang dapat ditemukan di sini .

 create stored procedure GetSearchMachingTagNames 
    @PipeDelimitedTagNames varchar(max), 
    @delimiter char(1) 
    as  
    begin
         select * from Tags 
         where Name in (select data from [dbo].[Split](@PipeDelimitedTagNames,@delimiter) 
    end
mangeshkt
sumber
8

Jika kita memiliki string yang disimpan di dalam klausa IN dengan tanda koma (,) dibatasi, kita bisa menggunakan fungsi charindex untuk mendapatkan nilai. Jika Anda menggunakan .NET, maka Anda dapat memetakan dengan SqlParameters.

Naskah DDL:

CREATE TABLE Tags
    ([ID] int, [Name] varchar(20))
;

INSERT INTO Tags
    ([ID], [Name])
VALUES
    (1, 'ruby'),
    (2, 'rails'),
    (3, 'scruffy'),
    (4, 'rubyonrails')
;

T-SQL:

DECLARE @Param nvarchar(max)

SET @Param = 'ruby,rails,scruffy,rubyonrails'

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

Anda dapat menggunakan pernyataan di atas dalam kode .NET Anda dan memetakan parameter dengan SqlParameter.

Demo Fiddler

EDIT: Buat tabel yang disebut SelectedTags menggunakan skrip berikut.

Naskah DDL:

Create table SelectedTags
(Name nvarchar(20));

INSERT INTO SelectedTags values ('ruby'),('rails')

T-SQL:

DECLARE @list nvarchar(max)
SELECT @list=coalesce(@list+',','')+st.Name FROM SelectedTags st

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0
Gowdhaman008
sumber
Bisakah Anda menunjukkan contoh kerja ini di mana tidak ada daftar nilai yang mungkin kode-keras?
John Saunders
@ JohnSaunders, saya telah mengedit skrip tanpa menggunakan daftar hardcoded. Harap verifikasi.
Gowdhaman008
3
Satu batasan dengan opsi ini. CharIndex mengembalikan 1 jika string ditemukan. IN mengembalikan kecocokan untuk persyaratan yang tepat. CharIndex untuk "Stack" akan mengembalikan 1 untuk istilah "StackOverflow" IN tidak akan. Ada sedikit tweek untuk jawaban ini menggunakan PatIndex di atas yang menyertakan nama dengan '<'% name% '>' yang mengatasi batasan ini. Solusi kreatif untuk masalah ini.
Richard Vivian
7

Untuk sejumlah variabel argumen seperti ini, satu-satunya cara yang saya ketahui adalah menghasilkan SQL secara eksplisit atau melakukan sesuatu yang melibatkan mengisi tabel sementara dengan item yang Anda inginkan dan bergabung dengan temp table.

ConcernedOfTunbridgeWells
sumber
7

Di ColdFusion kami hanya melakukan:

<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
    <cfquery name="q">
        select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
    </cfquery>
rip747
sumber
7

Berikut adalah teknik yang membuat ulang tabel lokal untuk digunakan dalam string kueri. Melakukannya dengan cara ini menghilangkan semua masalah parsing.

String dapat dibuat dalam bahasa apa pun. Dalam contoh ini saya menggunakan SQL karena itu adalah masalah asli yang saya coba pecahkan. Saya membutuhkan cara bersih untuk meneruskan data tabel dengan cepat dalam sebuah string untuk dieksekusi nanti.

Menggunakan tipe yang ditentukan pengguna adalah opsional. Menciptakan tipe hanya dibuat sekali dan dapat dilakukan sebelumnya. Jika tidak, tambahkan saja tipe tabel penuh ke deklarasi dalam string.

Pola umum mudah diperluas dan dapat digunakan untuk melewati tabel yang lebih kompleks.

-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
    [StringValue] [nvarchar](max) NOT NULL
)

-- Create a sample list using the list table type.
DECLARE @list [dbo].[StringList]; 
INSERT INTO @list VALUES ('one'), ('two'), ('three'), ('four')

-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE @str nvarchar(max);
SET @str = 'DECLARE @list [dbo].[StringList]; INSERT INTO @list VALUES '

-- Add all the values we want to the string. This would be a loop in C++.
SELECT @str = @str + '(''' + StringValue + '''),' FROM @list

-- Remove the trailing comma so the query is valid sql.
SET @str = substring(@str, 1, len(@str)-1)

-- Add a select to test the string.
SET @str = @str + '; SELECT * FROM @list;'

-- Execute the string and see we've pass the table correctly.
EXEC(@str)
Ikan batu
sumber
7

Dalam SQL Server 2016+ kemungkinan lain adalah menggunakan OPENJSONfungsi.

Pendekatan ini dibuat di blog tentang di OPENJSON - salah satu cara terbaik untuk memilih baris berdasarkan daftar id .

Contoh lengkap bekerja di bawah ini

CREATE TABLE dbo.Tags
  (
     Name  VARCHAR(50),
     Count INT
  )

INSERT INTO dbo.Tags
VALUES      ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)

GO

CREATE PROC dbo.SomeProc
@Tags VARCHAR(MAX)
AS
SELECT T.*
FROM   dbo.Tags T
WHERE  T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
                  FROM   OPENJSON(CONCAT('[', @Tags, ']')) J)
ORDER  BY T.Count DESC

GO

EXEC dbo.SomeProc @Tags = '"ruby","rails","scruffy","rubyonrails"'

DROP TABLE dbo.Tags 
Martin Smith
sumber
7

Berikut ini alternatif lain. Cukup berikan daftar yang dibatasi koma sebagai parameter string ke prosedur tersimpan dan:

CREATE PROCEDURE [dbo].[sp_myproc]
    @UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(@UnitList))

Dan fungsinya:

CREATE Function [dbo].[CsvToInt] ( @Array varchar(MAX))
returns @IntTable table
(IntValue int)
AS
begin
    declare @separator char(1)
    set @separator = ','
    declare @separator_position int
    declare @array_value varchar(MAX)

    set @array = @array + ','

    while patindex('%,%' , @array) <> 0
    begin

        select @separator_position = patindex('%,%' , @array)
        select @array_value = left(@array, @separator_position - 1)

        Insert @IntTable
        Values (Cast(@array_value as int))
        select @array = stuff(@array, 1, @separator_position, '')
    end
    return
end
Metafora
sumber
6

Saya punya jawaban yang tidak memerlukan UDF, XML Karena IN menerima pernyataan pilih misalnya SELECT * FROM Test mana Data IN (SELECT Value FROM TABLE)

Anda benar-benar hanya membutuhkan cara untuk mengubah string menjadi sebuah tabel.

Ini dapat dilakukan dengan CTE rekursif, atau kueri dengan tabel angka (atau Master..spt_value)

Ini versi CTE.

DECLARE @InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'

SELECT @InputString = @InputString + ','

;WITH RecursiveCSV(x,y) 
AS 
(
    SELECT 
        x = SUBSTRING(@InputString,0,CHARINDEX(',',@InputString,0)),
        y = SUBSTRING(@InputString,CHARINDEX(',',@InputString,0)+1,LEN(@InputString))
    UNION ALL
    SELECT 
        x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
        y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
    FROM 
        RecursiveCSV 
    WHERE
        SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR 
        SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
    * 
FROM 
    Tags
WHERE 
    Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);
Runonthespot
sumber
6

Saya menggunakan versi yang lebih ringkas dari jawaban terpilih :

List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("@tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();

var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));

Itu loop melalui parameter tag dua kali; tapi itu tidak masalah sebagian besar waktu (itu tidak akan menjadi hambatan Anda; jika ya, buka gulungannya).

Jika Anda benar-benar tertarik dengan kinerja dan tidak ingin mengulang melalui loop dua kali, ini versi yang kurang cantik:

var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)  
{
    var paramName = "@tag" + i;

    //Include size and set value explicitly (not AddWithValue)
    //Because SQL Server may use an implicit conversion if it doesn't know
    //the actual size.
    var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; } 
    paramNames.Add(paramName);
    parameters.Add(p);
}

var inClause = string.Join(",", paramNames);
George Stocker
sumber
5

Ini jawaban lain untuk masalah ini.

(versi baru diposting pada 6/4/13).

    private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
    {
        var ds = new DataSet();
        using (var sqlConn = new SqlConnection(scsb.ConnectionString))
        {
            var sqlParameters = new List<SqlParameter>();
            var replacementStrings = new Dictionary<string, string>();
            if (pars != null)
            {
                for (int i = 0; i < pars.Length; i++)
                {
                    if (pars[i] is IEnumerable<object>)
                    {
                        List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
                        replacementStrings.Add("@" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("@_{0}_{1}", i, pos))));
                        sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("@_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
                    }
                    else
                    {
                        sqlParameters.Add(new SqlParameter(String.Format("@{0}", i), pars[i] ?? DBNull.Value));
                    }
                }
            }
            strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
            using (var sqlCommand = new SqlCommand(strSql, sqlConn))
            {
                if (pars != null)
                {
                    sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
                }
                else
                {
                    //Fail-safe, just in case a user intends to pass a single null parameter
                    sqlCommand.Parameters.Add(new SqlParameter("@0", DBNull.Value));
                }
                using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
                {
                    sqlDataAdapter.Fill(ds);
                }
            }
        }
        return ds;
    }

Bersulang.

Darek
sumber
4

Satu-satunya langkah yang menang bukanlah bermain.

Tidak ada variabilitas tak terbatas untuk Anda. Hanya variabilitas terbatas.

Dalam SQL Anda memiliki klausa seperti ini:

and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )

Dalam kode C # Anda melakukan sesuatu seperti ini:

  int origCount = idList.Count;
  if (origCount > 5) {
    throw new Exception("You may only specify up to five originators to filter on.");
  }
  while (idList.Count < 5) { idList.Add(-1); }  // -1 is an impossible value
  return ExecuteQuery<PublishDate>(getValuesInListSQL, 
               origCount,   
               idList[0], idList[1], idList[2], idList[3], idList[4]);

Jadi pada dasarnya jika hitungannya 0 maka tidak ada filter dan semuanya berjalan lancar. Jika hitung lebih tinggi dari 0 maka nilainya harus ada dalam daftar, tetapi daftar telah diisi hingga lima dengan nilai yang tidak mungkin (sehingga SQL masih masuk akal)

Terkadang solusi lumpuh adalah satu-satunya yang benar-benar berfungsi.

Jason Henriksen
sumber