SQL IN yan tümcesini parametrelendirme


1041

Bunun INgibi değişken sayıda argüman içeren bir cümle içeren bir sorguyu nasıl parametrelendirebilirim ?

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

Bu sorguda, bağımsız değişken sayısı 1'den 5'e kadar herhangi bir yerde olabilir.

Bu (veya XML) için özel bir saklı yordam kullanmak tercih etmem, ancak SQL Server 2008'e özgü bazı zarif bir yol varsa , ben buna açıkım.



Yanıtlar:


316

İşte kullandığım hızlı ve kirli bir teknik:

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

İşte C # kodu:

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

İki uyarı:

  • Performans korkunç. LIKE "%...%"sorgular dizine eklenmez.
  • Hiç |boş, boş veya boş etiketinizin olmadığından emin olun , aksi takdirde çalışmaz

Bunu başarmak için bazı insanların daha temiz olduğunu düşündüğü başka yollar da var, bu yüzden lütfen okumaya devam edin.


119
Bu hella yavaş olacak
Matt Rogish

13
Evet, bu bir tablo taraması. 10 satır için harika, 100.000 için berbat.
Will Hartung

17
İçinde boru bulunan etiketleri test ettiğinizden emin olun.
Joel Coehoorn

17
Bu soruya cevap bile vermiyor. Verilmiş, parametreleri nereye ekleyeceğinizi görmek kolaydır, ancak sorguyu parametreleştirmeye bile zahmet etmiyorsa bu çözümü nasıl kabul edebilirsiniz? Sadece @Mark Brackett'inkinden daha basit görünüyor çünkü parametrelendirilmedi.
tvanfosson

21
Etiketiniz 'ruby | rails' ise. Eşleşecek, ki bu yanlış olacak. Bu tür çözümleri sunduğunuzda, etiketlerin boru içermediğinden emin olmanız veya bunları açıkça filtrelemeniz gerekir: Etiketlerden * 'ruby | rails | scruffy | rubyonrails |' '% |' gibi + İsim + '|%' VE '%!%' Gibi bir isim yok
AK

729

Her bir değeri parametrelendirebilirsiniz , böylece şöyle bir şey olabilir:

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

Hangi size verecektir:

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"

Hayır, bu SQL enjeksiyonuna açık değildir . CommandText içine yalnızca enjekte edilen metin kullanıcı girdisine dayalı değildir. Yalnızca sabit kodlu "@tag" önekini ve bir dizinin dizinini temel alır. Dizin her zaman bir tamsayı olur, kullanıcı tarafından oluşturulmaz ve güvenlidir.

Kullanıcı tarafından girilen değerler hala parametrelere doldurulur, bu nedenle orada bir güvenlik açığı yoktur.

Düzenle:

Enjeksiyon endişeleri bir yana, değişken sayıda parametreyi karşılamak için komut metninin (yukarıdaki gibi) yapılandırılmasının SQL sunucusunun önbelleğe alınmış sorgulardan yararlanma yeteneğini engellediğine dikkat edin. Net sonuç, ilk etapta parametreleri kullanma değerini neredeyse tamamen kaybetmenizdir (sadece yüklem dizelerini SQL'in kendisine eklemek yerine).

Önbelleğe alınmış sorgu planları değerli değildir, ancak IMO bu sorgu, bundan çok fazla fayda görecek kadar karmaşık değildir. Derleme maliyetleri yürütme maliyetlerine yaklaşabilir (hatta aşabilir), ancak yine de milisaniye konuşuyorsunuz.

Yeterli RAM'iniz varsa, SQL Server'ın sık sık parametre sayısı için bir plan önbelleğe almasını beklerdim. Her zaman beş parametre ekleyebileceğinizi ve belirtilmemiş etiketlerin NULL olmasına izin verdiğinizi varsayalım - sorgu planı aynı olmalı, ancak benim için oldukça çirkin görünüyor ve mikro optimizasyona değeceğinden emin değilim ( Yığın Taşması - buna değer olabilir).

Ayrıca, SQL Server 7 ve sonraki sürümler sorguları otomatik olarak parametrelendirecektir , bu nedenle performans açısından parametrelerin kullanılması gerçekten gerekli değildir - ancak güvenlik açısından kritiktir - özellikle de kullanıcı tarafından girilen verilerle.


2
Temel olarak "ilgili" soruya cevabımla aynı ve yorumlayıcı olmaktan ziyade yapıcı ve verimli olduğu için en iyi çözüm (çok daha zor).
tvanfosson

49
LINQ to SQL bunu yapar, BTW
Mark Cidade

3
@Pure: Bütün mesele, dinamik SQL kullandıysanız savunmasız kalacağınız SQL Injection'dan kaçınmaktır.
Ray

4
@God of Data - Evet, sanırım 2100'den fazla etikete ihtiyacınız varsa farklı bir çözüme ihtiyacınız olacak. Ancak Basarb'lar, ortalama etiket uzunluğu <3 karakter ise, yalnızca bir sınırlayıcıya ihtiyacınız olduğundan 2100'e ulaşabilir. msdn.microsoft.com/tr-tr/library/ms143432.aspx
Mark Brackett

2
@bonCodigo - seçtiğiniz değerler bir dizide; sadece dizi üzerinde döngü ve her biri için bir parametre (dizin ile sonlandırılmış) ekleyin.
Mark Brackett

249

SQL Server 2008 için tablo değerli bir parametre kullanabilirsiniz . Biraz çalışma, ama tartışmasız diğer yöntemimden daha temiz .

İlk olarak, bir tür oluşturmanız gerekir

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

Ardından, ADO.NET kodunuz şöyle görünür:

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

41
bunu test ettik ve tablo değerli parametreler DOG yavaş. 5 sorguyu yürütmek, bir TVP'den çok daha hızlıdır.
Jeff Atwood

4
@JeffAtwood - Sorguyu benzer bir şeye yeniden karıştırmayı denediniz SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);mi? Teorik olarak, bu gerçekten en hızlı yaklaşım olmalıdır. Alakalı dizinler kullanabilirsiniz (örneğin, etiket adındaki bir dizin INCLUDEideal olacaktır) ve SQL Server, tüm etiketleri ve sayılarını almak için birkaç kez uğraşmalıdır. Plan neye benziyor?
Nick Chammas

9
Ben de bunu test ettik ve AYDINLATMA HIZLI (büyük bir IN dizesi inşa karşılaştırıldığında). Ben sürekli "Ben bir Int32 [] IEnumerable`1 parametre değeri dönüştürülemedi" alıyorum çünkü parametre ayarlamada bazı sorunlar vardı. Her neyse, bunu çözdüm
Fredrik Johansson

6
@FredrikJohansson - 130 oydan, aslında bunu çalıştırmaya çalışan tek kişi olabilirsiniz! Dokümanları okurken bir hata yaptım ve aslında sadece IEnumerable değil, bir IEnumerable <SqlDataRecord> gerekir. Kod güncellendi.
Mark Brackett

3
@MarkBrackett Bir güncelleme ile harika! Bir Lucene arama dizinini sorguladığım için bazen bu kod gerçekten benim için günü kurtardı ve bazen SQL sunucusuna karşı iki kez kontrol edilmesi gereken 50.000'den fazla sonuç döndürüyor - Bu yüzden int [] (belge / SQL anahtarları) ve sonra yukarıdaki kod gelir. Tüm OP şimdi 200ms'den daha az sürüyor :)
Fredrik Johansson

188

Orijinal soru "Bir sorguyu nasıl parametrelendiririm ..." idi.

Burada belirteyim, bunun asıl sorunun cevabı değil . Diğer iyi cevaplarda zaten bazı gösteriler var.

Bununla birlikte, devam edin ve bu cevabı işaretleyin, aşağı indirin, bir cevap değil olarak işaretleyin ... doğru olduğuna inandığınız her şeyi yapın.

Ben (ve diğer 231 kişi) tarafından iptal edilen tercih edilen cevap için Mark Brackett'in cevabına bakınız. Cevabında verilen yaklaşım, 1) bağlama değişkenlerinin etkili kullanımına ve 2) anlaşılabilir tahminlere izin verir.

Seçilen cevap

Burada ele almak istediğim, Joel Spolsky'nin cevabında verilen yaklaşım, doğru cevap "seçilmiş" cevabı.

Joel Spolsky'nin yaklaşımı zekidir. Ve makul bir şekilde çalışır, "normal" değerler verildiğinde ve NULL ve boş dize gibi normatif kenar durumlarıyla öngörülebilir davranış ve öngörülebilir performans sergileyecektir. Ve belirli bir uygulama için yeterli olabilir.

Ancak bu yaklaşımı genelleştirmek açısından, Namesütunun bir joker karakter içerdiği (LIKE yüklemi tarafından tanındığı gibi) gibi daha belirsiz köşe vakalarını da ele alalım . En sık kullandığım joker karakter %(yüzde işareti). Şimdi burada bununla başa çıkalım ve daha sonra diğer davalara geçelim.

% Karakteriyle ilgili bazı sorunlar

Ad değerini düşünün 'pe%ter'. (Buradaki örnekler için, sütun adının yerine değişmez bir dize değeri kullanıyorum.) Ad değeri `` pe% ter '' olan bir satır, formun bir sorgusu tarafından döndürülür:

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

Ama bu aynı satır olacak değil arama terimlerinin sırası tersine eğer iade edilmesi:

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

Gözlemlediğimiz davranış biraz tuhaf. Listedeki arama terimlerinin sırasını değiştirmek sonuç kümesini değiştirir.

Neredeyse pe%terne olursa olsun, fıstık ezmesini eşleştirmek istemeyeceğimizi söylemeye gerek yok .

Gizli köşe kılıfı

(Evet, bunun belirsiz bir durum olduğunu kabul edeceğim. Muhtemelen test edilmesi muhtemel olmayan bir durum. Sütun değerinde bir joker karakter beklemezdik. Uygulamanın böyle bir değerin depolanmasını önlediğini varsayabiliriz. Deneyimlerime göre, nadiren bir LIKEkarşılaştırma operatörünün sağ tarafında joker karakterler olarak kabul edilecek karakterlere veya desenlere izin verilmeyen bir veritabanı kısıtlaması gördüm .

Delik açma

Bu deliği yamalamak için bir yaklaşım %joker karakterden kaçmaktır . (İşlecdeki çıkış yan tümcesine aşina olmayan herkes için, burada SQL Server belgelerine bir bağlantı verilmiştir .

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

Şimdi% değişmezi ile eşleşebiliriz. Tabii ki, bir sütun ismimiz olduğunda, joker karakterden dinamik olarak kaçmamız gerekecek. Bu REPLACEişlevi %karakterin oluşumlarını bulmak ve her birinin önüne bir ters eğik çizgi karakteri eklemek için kullanabiliriz:

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

Bu,% joker karakteriyle sorunu çözer. Neredeyse.

Kaçış kaçış

Çözümümüzün başka bir sorun yarattığını kabul ediyoruz. Kaçış karakteri. Ayrıca kaçış karakterinin kendisinden de kaçmamız gerektiğini görüyoruz. Bu sefer! kaçış karakteri olarak:

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

Alt çizgi de

Artık bir rulodayız, REPLACEalt çizgi joker karakterini başka bir tutamaç ekleyebiliriz . Ve sadece eğlence için, bu sefer $ kaçış karakteri olarak kullanacağız.

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

Oracle ve MySQL'in yanı sıra SQL Server'da da çalıştığı için bu yaklaşımdan kaçmayı tercih ederim. (Genellikle \ backslash karakterini kaçış karakteri olarak kullanıyorum, çünkü bu normal ifadelerde kullandığımız karakter. Ama neden konvansiyonla kısıtlanmalı!

Bu sinir bozucu parantez

SQL Server, joker karakterlerin köşeli parantez içine alınmasıyla değişmez değerler olarak ele alınmasını da sağlar []. Bu yüzden henüz en azından SQL Server için düzeltme yapmadık. Parantez çiftleri özel bir anlama sahip olduğundan, bunlardan da kaçmamız gerekecek. Parantezlerden düzgün bir şekilde kaçmayı başarabilirsek, en azından parantez içindeki tire -ve karat ile uğraşmak zorunda kalmayacağız ^. Ve biz herhangi bırakabilir %ve _parantez kaçan içine temelde parantez özel bir anlam devre dışı bırakmış olacağı için, karakterleri.

Eşleşen parantez çiftlerini bulmak o kadar da zor olmamalı. Singleton% ve _ oluşumlarını işlemekten biraz daha zor. (Parantezlerin tüm örneklerinden kaçmanın yeterli olmadığına dikkat edin, çünkü tek bir parantez bir değişmez olarak kabul edilir ve kaçması gerekmez. .)

Satır içi ifade dağınık hale geliyor

SQL'deki bu satır içi ifade daha uzun ve çirkinleşiyor. Muhtemelen işe yarayabiliriz, ama cennet geride kalan ve deşifre etmek zorunda olan fakir ruha yardım eder. Satır içi ifadeler için bir hayranım kadar, burada bir tane kullanmamaya meyilliyim, çünkü karışıklığın nedenini açıklayan ve bunun için özür dileyerek bir yorum bırakmak istemiyorum.

Nerede bir fonksiyon?

Tamam, bu yüzden, bunu SQL'de satır içi bir ifade olarak ele almazsak, sahip olduğumuz en yakın alternatif kullanıcı tanımlı bir işlevdir. Ve bunun hiçbir şeyi hızlandırmayacağını biliyoruz (Oracle ile yapabileceğimiz gibi bir dizin tanımlayamazsak.) Bir işlev oluşturmanız gerekirse, SQL'i çağıran kodda bunu daha iyi yapabiliriz Beyan.

Ve bu işlevin DBMS ve sürüme bağlı olarak bazı davranış farklılıkları olabilir. (Herhangi bir veritabanı motorunu birbirinin yerine kullanabilmeye hevesli olan tüm Java geliştiricilerine seslenin.)

Alan bilgisi

Biz olduğunu, sütun için uygulanan izin verilen değerlerin kümesi. Biz biliyor olabilir (sütun için etki alanının uzman bilgiye sahip olabilir önsel sütunda depolanan değerler yüzde işareti, bir alt çizgi ya da destek ihtiva asla Bu durumda, bu davaların ele alındığına dair hızlı bir yorum ekliyoruz.

Sütunda depolanan değerler% veya _ karaktere izin verebilir, ancak bir sınırlama, değerlerin LIKE karşılaştırması "güvenli" olacak şekilde, tanımlanmış bir karakter kullanarak bu değerlerin kaçmasını gerektirebilir. Yine, izin verilen değerler kümesi ve özellikle hangi karakterin kaçış karakteri olarak kullanıldığı ve Joel Spolsky'nin yaklaşımıyla ilgili hızlı bir yorum.

Ancak, uzmanlık bilgisi ve bir garanti yoksa, en azından bu belirsiz köşe davalarını ele almayı ve davranışın makul ve "şartname uyarınca" olup olmadığını düşünmemiz önemlidir.


Yeniden özetlenen diğer konular

Başkalarının zaten yaygın olarak düşünülen diğer endişe alanlarından bazılarını yeterince işaret ettiklerine inanıyorum:

  • SQL enjeksiyonu (kullanıcı tarafından sağlanan bilgiler gibi görünen ve bind değişkenleri aracılığıyla bunları sağlamak yerine SQL metnine dahil edilenler. Bind değişkenlerini kullanmak gerekli değildir, SQL enjeksiyonu ile mücadele etmek için sadece uygun bir yaklaşımdır. onunla başa çıkmanın yolları:

  • dizin arama yerine dizin taraması kullanan optimize edici planı, joker karakterlerden kaçmak için bir ifade veya işlev ihtiyacı (ifade veya işlev üzerindeki olası dizin)

  • bağlama değişkenleri yerine değişmez değerlerin kullanılması ölçeklenebilirliği etkiler


Sonuç

Joel Spolsky'nin yaklaşımını seviyorum. Akıllı. Ve çalışıyor.

Ama onu görür görmez, hemen onunla ilgili potansiyel bir sorun gördüm ve kaymasına izin vermek benim doğam değil. Başkalarının çabalarını eleştirmek istemiyorum. Birçok geliştiricinin işlerini çok kişisel aldığını biliyorum, çünkü ona çok yatırım yapıyorlar ve çok önemsiyorlar. Lütfen anlayın, bu kişisel bir saldırı değil. Burada belirlediğim, testten ziyade üretimde ortaya çıkan sorun türüdür.

Evet, asıl sorudan uzaklaştım. Ama bir soru için "seçilmiş" cevap ile önemli bir konu olarak düşündüğüm konu hakkında bu notu başka nerede bırakabilirim?


Parametreli querys kullanıyorsanız veya beğenirseniz lütfen bize bildirir misiniz? bu özel durumda, 'parametrelenmiş sorguları kullan' kuralının üzerinden atlamak ve orijinal dil ile sterilize etmek doğru mu? TEŞEKKÜRLER
Luis Siquot

2
@Luis: evet, SQL deyimlerinde bind değişkenlerini kullanmayı tercih ediyorum ve bind değişkenlerini yalnızca bir performans sorununa neden olurken kullanmamaya çalışacağım. özgün sorun için benim normatif desen dinamik olarak IN listesinde gerekli sayıda yer tutucu sayısı ile SQL deyimi oluşturmak ve daha sonra her değeri yer tutuculardan birine bağlamak olacaktır. Mark Brackett'in cevabına bakın, ki bu ben (ve diğer 231 kişi) tarafından iptal edildi.
spencer7593

133

Parametreyi dize olarak iletebilirsiniz

Dize var

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

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

Sonra tek yapmanız gereken dizeyi 1 parametre olarak geçirmektir.

İşte kullandığım split fonksiyonu.

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

2
Bu yaklaşımla tablo işlevine de katılabilirsiniz.
Michael Haren

Oracle'da buna benzer bir çözüm kullanıyorum. Diğer çözümlerin bazılarının yaptığı gibi yeniden ayrıştırılması gerekmez.
Leigh Riffel

9
Bu saf bir veritabanı yaklaşım diğer veritabanı dışında kod çalışması gerekir.
David Basarab

Bu bir tablo taraması yapar mı, yoksa dizinlerden vb. Yararlanabilir mi?
Pure.Krome

daha iyi CROSS UYGULAMAYI esas olarak döndürülen tabloya katılan SQL tablo işlevine (en azından 2005'ten itibaren) karşı kullanmak olacaktır
adolf sarımsak

66

Jeff / Joel'in bugün bu podcast'de konuştuğunu duydum ( bölüm 34 , 2008-12-16 (MP3, 31 MB), 1 sa 03 dk 38 sn - 1 sa 06 dk 45 sn) ve Stack Overflow'u hatırladığımı düşündüm LINQ to SQL kullanıyordu , ama belki de terkedilmişti. İşte aynı şey LINQ to SQL.

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

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

Bu kadar. Ve evet, LINQ zaten yeterince geriye bakıyor, ancak Containsfıkra bana fazladan arkaya benziyor. İş yerinde bir proje için benzer bir sorgu yapmak zorunda kaldım, doğal olarak yerel dizi ve SQL Server tablosu arasında birleştirme yaparak, yanlış bir şekilde yapmaya çalıştım, LINQ to SQL çevirmen işleyecek kadar akıllı olacağını düşündüm çeviri bir şekilde. Olmadı, ama açıklayıcı olan ve beni Contains'ı kullanmaya yönlendiren bir hata mesajı verdi .

Her neyse, bunu şiddetle tavsiye edilen LINQPad'de çalıştırır ve bu sorguyu çalıştırırsanız, SQL LINQ sağlayıcısının oluşturduğu gerçek SQL'i görüntüleyebilirsiniz. Size bir INcümle olarak parametreleştirilen değerlerin her birini gösterecektir .


50

.NET'den arıyorsanız, Dapper dot net'i kullanabilirsiniz :

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

Burada Dapper düşünceyi yapar, bu yüzden yapmak zorunda değilsiniz. Elbette LINQ to SQL ile benzer bir şey mümkündür :

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;

11
sorulan asıl soru için bu sayfada kullandığımız şey (dapper) i.stack.imgur.com/RBAjL.png
Sam Saffron


İsimler uzunsa bu düşer
cs0815

29

Bu muhtemelen bunu yapmanın yarı kötü bir yolu, bir kez kullandım, oldukça etkili oldu.

Hedeflerinize bağlı olarak yararlı olabilir.

  1. Tek sütunlu bir geçici tablo oluşturun .
  2. INSERT her bir arama değeri bu sütuna girer.
  3. Bir yerine, INstandart JOINkurallarınızı kullanabilirsiniz . (Esneklik ++)

Bu, yapabileceğiniz şeylerde biraz daha fazla esnekliğe sahiptir, ancak sorgulamak için iyi bir dizine sahip büyük bir tablonuzun olduğu ve parametrelenmiş listeyi birden fazla kullanmak istediğiniz durumlar için daha uygundur. İki kez yürütmek zorunda kalır ve tüm sanitasyon elle yapılır.

Tam olarak ne kadar hızlı olduğunu söyleyemedim, ama benim durumumda gerekliydi.


Bu hiç de kötü değil! Dahası, IMHO çok temiz bir yol. Ve yürütme planına bakarsanız, bunun IN cümlesi ile aynı olduğunu görürsünüz. Bir geçici tablo yerine, parametreleri SESSIONID ile birlikte depoladığınız dizinlerle sabit bir tablo da oluşturabilirsiniz.
SQL Polisi

27

Gelen SQL Server 2016+şunu kullanabilirsiniz STRING_SPLITişlevi:

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

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

veya:

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

Kabul edilen cevap elbette işe yarayacaktır ve gidilecek yollardan biridir, ancak anti-kalıptır.

E. Değerler listesine göre satır bulma

Bu, uygulama katmanında veya Transact-SQL'de dinamik bir SQL dizesi oluşturmak veya LIKE işleci kullanmak gibi yaygın anti-desen yerine geçer:

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

Zeyilname :

STRING_SPLITTablo işlevi satır tahminini geliştirmek için , bölünmüş değerleri geçici tablo / tablo değişkeni olarak gerçekleştirmek iyi bir fikirdir:

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 - Canlı Demo

İlgili: Değerler Listesini Saklı Yordam'a Aktarma


Orijinal soru şartı vardır SQL Server 2008. Bu soru genellikle yinelenen olarak kullanıldığından, bu yanıtı referans olarak ekledim.


1
Bunu mükemmel bir şekilde test etmedim, ancak bunun en temiz 2016+ çözümü olduğunu hissediyorum. Ben hala sadece bir dizi int geçmek mümkün olmak istiyorum, ama o zamana kadar ...
Daniel

24

Birleştirebileceğiniz bir tablo değişkeni oluşturan bir işleve sahibiz:

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 

Yani:

@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

20

Bu iğrenç, ancak en az birine sahip olduğunuz garanti edilirse şunları yapabilirsiniz:

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

IN ('tag1', 'tag2', 'tag1', 'tag1', 'tag1') SQL Server tarafından kolayca optimize edilebilir. Ayrıca, doğrudan dizin aramaları alırsınız


1
Optimize edici, verimli sorgular oluşturmak için kullanılan parametre sayısını gerektirdiğinden, Null özellikli isteğe bağlı parametreler bozulma performansını kontrol eder. 5 parametreli bir sorgu, 500 parametre için olandan farklı bir sorgu planına ihtiyaç duyabilir.
Erik Hart

18

Bence, bu sorunu çözmek için en iyi kaynak, bu sitede yayınlanan şeydir:

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

kullanın:

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

KREDİLERİ: Dinakar Nethi


Mükemmel yanıt, temiz ve modüler, bir masaya ilk CSV ayrıştırma haricinde süper hızlı yürütme (bir kez, az sayıda öğe). Her ne kadar patindex () yerine daha basit / daha hızlı charindex () kullanabilirsiniz? Charindex () ayrıca her dizgede girdi dizesini kesmekten kaçınabilecek 'start_location' argümanına izin verir mi? Orijinal soruyu cevaplamak için sadece fonksiyon sonucuna katılabilir.
crokusek

18

Bir tablo türü parametresi ( SQL Server 2008 olduğundan beri) geçecek ve bir where existsveya iç birleşim yapmak. Ayrıca sp_xml_preparedocumentgeçici tabloyu kullanarak XML kullanabilir ve hatta dizinleyebilirsiniz.


Ph.E'nin cevabı, csv'den bir örnek bina geçici tablosuna sahiptir.
crokusek

12

IMHO'nun uygun bir şekilde, listeyi bir karakter dizesinde saklamaktır (DBMS'nin desteklediği uzunlukla sınırlıdır); tek hile (işleme basitleştirmek için) dizenin başında ve sonunda bir ayırıcı (benim örnekte bir virgül) var. Fikir, listeyi değer başına bir satır içeren tek sütunlu bir tabloya dönüştürerek "anında normalleştirmektir". Bu dönmenizi sağlar

içinde (ct1, ct2, ct3 ... ctn)

Içine

içinde (seç ...)

veya (muhtemelen tercih edeceğim çözüm), listede yinelenen değerlerle ilgili sorunlardan kaçınmak için yalnızca "farklı" bir eklerseniz düzenli bir birleştirme yapabilirsiniz.

Ne yazık ki, bir ipi dilimleme teknikleri oldukça ürüne özgüdür. İşte SQL Server sürümü:

 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;

Oracle sürümü:

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

ve MySQL sürümü:

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

(Tabii ki, "pivot" listede bulabileceğimiz maksimum öğe sayısı kadar satır döndürmelidir)


11

SQL Server 2008 veya daha yeni bir sürümüne sahipseniz , Tablo Değerli Parametre kullanırdım .

SQL Server 2005'te takılı kalacak kadar şanssızsanız, bunun gibi bir CLR işlevi ekleyebilirsiniz ,

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

Hangi şekilde kullanabilirsiniz ki,

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

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

10

Bu statik bir sorgu sadece yol değil zaman bir durum olduğunu düşünüyorum. Listenizi yan tümceniz için dinamik olarak oluşturun, tek tırnaklarınızdan kaçın ve dinamik olarak SQL oluşturun. Bu durumda, muhtemelen küçük liste nedeniyle herhangi bir yöntemle çok fazla bir fark görmeyeceksiniz, ancak en etkili yöntem gerçekten SQL'i tam olarak yazınızda olduğu gibi göndermektir. En güzel kodu yapan şeyi yapmak yerine en verimli şekilde yazmak iyi bir alışkanlık olduğunu veya SQL'i dinamik olarak oluşturmak için kötü bir uygulama olduğunu düşünüyorum.

Bölünmüş işlevler parametreleri büyük olsun birçok durumda sorgu kendilerini yürütmek için daha uzun sürdüğünü gördüm. SQL 2008'de tablo değerli parametreler içeren bir saklı yordam, dikkate alacağınız diğer tek seçenektir, ancak durumunuzda muhtemelen daha yavaş olacaktır. TVP büyük listeler için büyük olasılıkla sadece TVP'nin birincil anahtarını arıyorsanız daha hızlı olacaktır, çünkü SQL yine de liste için geçici bir tablo oluşturacaktır (liste büyükse). Test etmedikçe kesin olarak bilemezsiniz.

Ayrıca null varsayılan değerleri ile 500 parametre ve WHERE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500) sahip saklı yordamlar gördüm. Bu, SQL'in geçici tablo oluşturmasına, bir sıralama / farklı işlem yapmasına ve ardından dizin araması yerine tablo taraması yapmasına neden oldu. Bu sorguyu parametreleştirerek yapacağınız şey budur, ancak fark edilebilir bir fark yaratmayacak kadar küçük bir ölçekte. IN listelerinizde NULL olmasına karşı tavsiye ederim, sanki bir NOT IN olarak değiştirildiği gibi amaçlandığı gibi hareket etmeyecektir. Parametre listesini dinamik olarak oluşturabilirsiniz, ancak kazanacağınız tek şey, nesnelerin sizin için tek tırnaktan kaçmasıdır. Nesnelerin parametreleri bulmak için sorguyu ayrıştırması gerektiğinden, bu yaklaşım uygulama sonunda biraz daha yavaştır.

Saklı yordamlar veya parametreli sorgular için yürütme planlarının yeniden kullanılması size bir performans kazancı sağlayabilir, ancak sizi yürütülen ilk sorgu tarafından belirlenen bir yürütme planına kilitler. Bu, çoğu durumda sonraki sorgular için ideal olandan daha az olabilir. Sizin durumunuzda, yürütme planlarının yeniden kullanılması muhtemelen bir artı olacaktır, ancak örnek gerçekten basit bir sorgu olduğu için hiç fark etmeyebilir.

Cliffs notları:

Sizin durumunuz için yaptığınız herhangi bir şey, listede sabit sayıda öğe ile parametrelendirme (kullanılmıyorsa null), sorguyu parametrelerle veya parametresiz olarak dinamik olarak oluşturmak veya tablo değerli parametrelerle saklı yordamlar kullanmak çok fazla fark yaratmaz . Ancak, genel önerilerim şu şekildedir:

Durumunuz / birkaç parametreli basit sorgular:

Dinamik SQL, belki test daha iyi performans gösteriyorsa parametrelerle.

Yeniden kullanılabilir yürütme planlarına sahip sorgular, parametreleri değiştirerek veya sorgu karmaşıksa birden çok kez çağrılır:

Dinamik parametrelerle SQL.

Büyük listeli sorgular:

Tablo değerli parametrelerle saklı yordam. Liste büyük miktarda değişebilirse, saklı yordamda RECOMPILE ile kullanın veya her sorgu için yeni bir yürütme planı oluşturmak için parametreler olmadan dinamik SQL kullanın.


Burada "saklı yordam" ile ne demek istiyorsun? Bir örnek gönderebilir misiniz?
struhtanov

9

XML'i burada kullanabiliriz:

    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)

1
CTEve bu makalede@x gösterildiği gibi çok dikkatli bir şekilde yapılırsa alt seçime elenebilir / eğilebilir .
robert4

9

Ben varsayılan olarak bir tablo değerli bir işlev (bir dize bir tablo döndürür) IN koşulu geçirerek yaklaşım.

İşte UDF kodu (Yığın Taşması'ndan bir yere aldım, şu anda kaynağı bulamıyorum)

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
  )

Bunu aldıktan sonra kodunuz şu kadar basit olacaktır:

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

Gülünç derecede uzun bir dizeniz olmadığı sürece, bu tablo diziniyle iyi çalışmalıdır.

Gerekirse bir geçici tabloya ekleyebilir, dizine ekleyebilir, ardından bir birleştirme çalıştırabilirsiniz ...


8

Başka bir olası çözüm, değişken sayıda bağımsız değişkeni saklı bir yordama geçirmek, peşinde olduğunuz adları içeren tek bir dize geçirmek, ancak '<>' ile çevreleyerek benzersiz kılmaktır. Ardından adları bulmak için PATINDEX kullanın:

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

8

Aşağıdaki saklı yordamı kullanın. Bu bulunabilir özel bir bölme işlevini kullanır burada .

 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

8

IN deyimi içinde virgül (,) ile ayrılmış dizelerimiz varsa, değerleri almak için charindex işlevini kullanabiliriz. .NET kullanıyorsanız, SqlParameters ile eşleştirebilirsiniz.

DDL Komut Dosyası:

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

.NET kodunuzda yukarıdaki ifadeyi kullanabilir ve parametreyi SqlParameter ile eşleyebilirsiniz.

Fiddler demosu

EDIT: Aşağıdaki komut dosyasını kullanarak SelectedTags adlı tabloyu oluşturun.

DDL Komut Dosyası:

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

Olası değerlerin sabit kodlu bir listesinin bulunmadığı bu çalışma örneğini gösterebilir misiniz?
John Saunders

@JohnSaunders, herhangi bir sabit kod listesi kullanmadan komut dosyasını düzenledim. Lütfen tanımla.
Gowdhaman008

3
Bu seçenekle ilgili bir sınırlama. Dize bulunursa CharIndex 1 değerini döndürür. IN, kesin terimler için bir eşleşme döndürür. "Stack" için CharIndex "StackOverflow" IN terimi için 1 döndürür. Yukarıda PatIndex'i kullanarak bu sınırlamanın üstesinden gelen '<'% name% '>' ile isimleri çevreleyen küçük bir cümle var. Yine de bu soruna yaratıcı bir çözüm.
Richard Vivian

7

Bunun gibi değişkenlerin değişken sayısı için farkında olduğum tek yol, SQL'i açıkça oluşturmak veya istediğiniz öğelerle geçici bir tablo doldurmayı ve geçici tabloya katılmayı içeren bir şey yapmaktır.


7

In ColdFusion'ın biz sadece yapın:

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

7

Bir sorgu dizesinde kullanılacak yerel bir tabloyu yeniden oluşturan bir teknik. Bu şekilde yapmak tüm ayrıştırma sorunlarını ortadan kaldırır.

Dize herhangi bir dilde oluşturulabilir. Bu örnekte, çözmeye çalıştığım orijinal sorun olduğu için SQL kullandım. Daha sonra yürütülecek bir dize anında tablo veri geçmek için temiz bir yol gerekli.

Kullanıcı tanımlı bir tür kullanmak isteğe bağlıdır. Türü oluşturmak yalnızca bir kez oluşturulur ve önceden yapılabilir. Aksi takdirde, dizedeki bildirime tam tablo türü eklemeniz yeterlidir.

Genel paternin uzatılması kolaydır ve daha karmaşık tabloları geçmek için kullanılabilir.

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

7

SQL Server 2016 + 'da başka bir olasılık OPENJSONişlevi kullanmaktır .

Bu yaklaşım, satırları kimlik listesine göre seçmenin en iyi yollarından biri olan OPENJSON'da bloglanır .

Aşağıda tam olarak çalışılmış bir örnek

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 

7

İşte başka bir alternatif. Saklı yordama dize parametresi olarak virgülle ayrılmış bir liste iletmeniz yeterlidir:

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

Ve fonksiyon:

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

6

Ben bir UDF, XML gerektirmeyen bir cevap var çünkü IN bir seçim deyimi kabul eder örneğin SELECT * FROM Test nerede veri IN (SELECT değeri FROM TABLE)

Gerçekten sadece dizeyi bir tabloya dönüştürmek için bir yol gerekir.

Bu, özyinelemeli bir CTE veya sayı tablosuna (veya Master..spt_value) sahip bir sorgu ile yapılabilir

İşte CTE sürümü.

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

6

En çok oy alan cevabın daha kısa bir versiyonunu kullanıyorum :

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

İki kez tag parametreleri arasında döngü yapar; ancak bu çoğu zaman önemli değildir (tıkanıklığınız olmayacak; öyleyse döngüyü açın).

Performansla gerçekten ilgileniyorsanız ve döngüde iki kez tekrarlamak istemiyorsanız, daha az güzel bir sürüm:

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

5

İşte bu sorunun başka bir cevabı.

(yeni sürüm 6/4/13 tarihinde yayınlanmıştır).

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

Şerefe.


4

Tek kazanan hamle oynamak değil.

Sizin için sonsuz değişkenlik yok. Sadece sonlu değişkenlik.

SQL'de şöyle bir cümleniz var:

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

C # kodunda böyle bir şey yaparsınız:

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

Temelde sayım 0 ise, filtre yoktur ve her şey geçer. Sayı 0'dan büyükse, değer listede olmalıdır, ancak liste imkansız değerlerle beşe doldurulmuştur (böylece SQL hala mantıklıdır)

Bazen topal çözüm aslında işe yarayan tek çözümdür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.