Birden çok Id değerini kabul eden T-SQL saklı yordamı


145

Bir kimlik listesini bir saklı yordama parametre olarak aktarmanın zarif bir yolu var mı?

Örneğin, 1, 2, 5, 7, 20 numaralı bölümlerin saklı yordamım tarafından iade edilmesini istiyorum. Geçmişte, aşağıdaki kod gibi virgülle ayrılmış bir kimlikler listesinde geçtim, ancak bunu yaparken gerçekten kirli hissediyorum.

Sanırım SQL Server 2005 uygulanabilir tek sınırlamam.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)

İşte bulduğum XML yönteminin bir çeşidi.
JasonS

5
SQL Server 2008 üzerindeyseniz, Tablo Değerli Parametre kullanabilirsiniz. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Yanıtlar:


237

Erland Sommarskog, son 16 yıldır bu soruya yetkili cevabı sürdürüyor: SQL Server'daki Diziler ve Listeler .

Bir dizi veya listeyi bir sorguya aktarmanın en az bir düzine yolu vardır; her birinin kendine özgü artıları ve eksileri vardır.

  • Tablo Değerli Parametreler . Yalnızca SQL Server 2008 ve üstü ve muhtemelen evrensel "en iyi" yaklaşıma en yakın olanı.
  • Yinelemeli Yöntem . Sınırlandırılmış bir dize geçirin ve içinden döngü yapın.
  • CLR'yi kullanma . Yalnızca .NET dillerinden SQL Server 2005 ve sonraki sürümler.
  • XML . Birçok satır eklemek için çok iyi; SELECT'ler için fazla olabilir.
  • Sayı Tablosu . Basit yinelemeli yöntemden daha yüksek performans / karmaşıklık.
  • Sabit uzunlukta Elemanlar . Sabit uzunluk, sınırlandırılmış dizeye göre hızı artırır
  • Sayıların İşlevi . Sayı Tablosu varyasyonları ve sayıların tablodan alınmak yerine bir işlevde üretildiği sabit uzunlukta.
  • Özyinelemeli Ortak Tablo İfadesi (CTE). SQL Server 2005 ve üstü, yine de çok karmaşık değil ve yinelemeli yöntemden daha yüksek performans.
  • Dinamik SQL . Yavaş olabilir ve güvenlik açısından etkileri olabilir.
  • Listeyi Birçok Parametre Olarak Geçirme . Sıkıcı ve hataya açık, ancak basit.
  • Gerçekten Yavaş Yöntemler . Charindex, patindex veya LIKE kullanan yöntemler.

Tüm bu seçenekler arasında değiş tokuşlar hakkında bilgi edinmek için makaleyi okumayı gerçekten tavsiye edemem .


11

Evet, mevcut çözümünüz SQL enjeksiyon saldırılarına meyillidir.

Bulduğum en iyi çözüm kelimelere böler metin (birkaç burada yayınlanan veya kullanabileceğiniz olduğunu bir işlevi kullanmaktır benim blogdan bu bir ve daha sonra tabloya o katılmak). Gibi bir şey:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId

14
Depolanan proc doğrudan güvenilmeyen istemcilerden çağrılmadıkça "SQL enjeksiyon saldırılarına eğilimli" olduğundan emin değilim, bu durumda daha büyük sorunlarınız olur. Hizmet katmanı kodu, güçlü yazılmış verilerden (örn. İnt [] departmanIds) @DepartmentIds dizesini oluşturmalıdır, bu durumda sorun olmayacaktır.
Anthony

Harika çözüm @Matt Hamilton. Bunun kimseye yardımcı olup olmayacağını bilmiyorum, ancak metin alanlarında arama yaparken "dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0" kullanarak SQL Server 2008r'de daha doğru sonuçlar aldım
Darkloki

3

Değerlerle çok fazla çalışacaksanız, göz önünde bulundurmanız gereken bir yöntem, onları önce geçici bir tabloya yazmaktır. O zaman normal gibi ona katılırsın.

Bu şekilde, yalnızca bir kez ayrıştırırsınız.

'Bölünmüş' UDF'lerden birini kullanmak en kolay yoldur, ancak pek çok kişi bunların örneklerini yayınladı, farklı bir rotaya gideceğimi düşündüm;)

Bu örnek, katılmanız için (#tmpDept) geçici bir tablo oluşturacak ve bunu geçtiğiniz departman kimlikleriyle dolduracaktır. Onları virgülle ayırdığınızı varsayıyorum, ancak - elbette - değiştirebilirsiniz ne istersen ona.

IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
    DROP TABLE #tmpDept
END

SET @DepartmentIDs=REPLACE(@DepartmentIDs,' ','')

CREATE TABLE #tmpDept (DeptID INT)
DECLARE @DeptID INT
IF IsNumeric(@DepartmentIDs)=1
BEGIN
    SET @DeptID=@DepartmentIDs
    INSERT INTO #tmpDept (DeptID) SELECT @DeptID
END
ELSE
BEGIN
        WHILE CHARINDEX(',',@DepartmentIDs)>0
        BEGIN
            SET @DeptID=LEFT(@DepartmentIDs,CHARINDEX(',',@DepartmentIDs)-1)
            SET @DepartmentIDs=RIGHT(@DepartmentIDs,LEN(@DepartmentIDs)-CHARINDEX(',',@DepartmentIDs))
            INSERT INTO #tmpDept (DeptID) SELECT @DeptID
        END
END

Bu, bir departman kimliğini, aralarında virgül bulunan birden çok kimliği veya hatta aralarında virgül ve boşluk bulunan birden çok kimliği geçirmenize olanak tanır.

Yani şöyle bir şey yaptıysanız:

SELECT Dept.Name 
FROM Departments 
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name

Geçtiğiniz tüm departman kimliklerinin adlarını göreceksiniz ...

Yine, bu, geçici tabloyu doldurmak için bir işlev kullanılarak basitleştirilebilir ... Genelde bunu sadece biraz can sıkıntısını gidermek için yaptım: -

- Kevin Fairchild


3

XML kullanabilirsiniz.

Örneğin

declare @xmlstring as  varchar(100) 
set @xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>' 

declare @docid int 

exec sp_xml_preparedocument @docid output, @xmlstring

select  [id],parentid,nodetype,localname,[text]
from    openxml(@docid, '/args', 1) 

Sp_xml_preparedocument komutu yerleşiktir.

Bu çıktıyı üretir:

id  parentid    nodetype    localname   text
0   NULL        1           args        NULL
2   0           1           arg         NULL
3   2           2           value       NULL
5   3           3           #text       42
4   0           1           arg2        NULL
6   4           3           #text       -1

ihtiyacınız olan şeylerin hepsi (daha fazlası?)


2

Depolanan bir yordamı kullanmak ve virgülle ayrılmış Departman Kimliklerinin listesini iletmek istiyorsanız süper hızlı bir XML Yöntemi:

Declare @XMLList xml
SET @XMLList=cast('<i>'+replace(@DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i))

Tüm kredi Guru Brad Schulz'un Blog'una gidiyor


-3

Bunu dene:

@list_of_params varchar(20) -- value 1, 2, 5, 7, 20 

SELECT d.[Name]
FROM Department d
where @list_of_params like ('%'+ CONVERT(VARCHAR(10),d.Id)  +'%')

Çok basit.


1
çok basit - ve çok yanlış. Ancak sorunu kodunuzda çözseniz bile çok yavaş olacaktır. Ayrıntılar için kabul edilen yanıtta "Gerçekten Yavaş Yöntemler" bağlantısına bakın.
Sebastian Meine
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.