Prosedur tersimpan T-SQL yang menerima beberapa nilai ID

145

Apakah ada cara yang anggun untuk menangani melewati daftar id sebagai parameter ke prosedur tersimpan?

Misalnya, saya ingin departemen 1, 2, 5, 7, 20 dikembalikan oleh prosedur tersimpan saya. Di masa lalu, saya telah mengirimkan daftar id yang dibatasi koma, seperti kode di bawah ini, tetapi merasa sangat kotor melakukannya.

SQL Server 2005 adalah satu-satunya batasan saya yang berlaku.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)
JasonS
sumber
Ini adalah varian dari metode XML yang baru saja saya temukan.
JasonS
5
Jika Anda menggunakan SQL Server 2008, Anda bisa menggunakan Parameter Table-Valued. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Jawaban:

237

Erland Sommarskog telah mempertahankan jawaban otoritatif untuk pertanyaan ini selama 16 tahun terakhir: Array dan Daftar di SQL Server .

Setidaknya ada selusin cara untuk melewatkan array atau daftar ke kueri; masing-masing memiliki pro dan kontra yang unik.

  • Parameter Table-Valued . SQL Server 2008 dan lebih tinggi saja, dan mungkin yang paling dekat dengan pendekatan universal "terbaik".
  • Metode Iteratif . Lewati string yang dibatasi dan lewati.
  • Menggunakan CLR . SQL Server 2005 dan lebih tinggi dari bahasa .NET saja.
  • XML . Sangat bagus untuk memasukkan banyak baris; mungkin berlebihan untuk SELECT.
  • Daftar Angka . Kinerja / kompleksitas yang lebih tinggi daripada metode iteratif sederhana.
  • Elemen dengan panjang tetap . Panjang yang diperbaiki meningkatkan kecepatan pada string yang dibatasi
  • Fungsi Angka . Variasi Daftar Bilangan dan panjang tetap di mana nomor dihasilkan dalam fungsi daripada diambil dari tabel.
  • Recursive Common Table Expression (CTE). SQL Server 2005 dan lebih tinggi, masih tidak terlalu rumit dan kinerjanya lebih tinggi daripada metode iteratif.
  • SQL dinamis . Dapat lambat dan memiliki implikasi keamanan.
  • Lulus Daftar sebagai Banyak Parameter . Bosan dan rawan kesalahan, tapi sederhana.
  • Metode Sangat Lambat . Metode yang menggunakan charindex, patindex atau LIKE.

Saya benar-benar tidak bisa merekomendasikan cukup untuk membaca artikel untuk belajar tentang pengorbanan di antara semua opsi ini.

Portman
sumber
11

Ya, solusi Anda saat ini rentan terhadap serangan injeksi SQL.

Solusi terbaik yang saya temukan adalah menggunakan fungsi yang membagi teks menjadi kata-kata (ada beberapa yang diposting di sini, atau Anda dapat menggunakan ini dari blog saya ) dan kemudian bergabung dengan itu ke meja Anda. Sesuatu seperti:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Matt Hamilton
sumber
14
Saya tidak yakin itu "rentan terhadap serangan injeksi SQL" kecuali proc yang disimpan dapat dipanggil langsung dari klien yang tidak dipercaya, dalam hal ini Anda memiliki masalah yang lebih besar. Kode lapisan layanan harus menghasilkan string @DepartmentIds dari data yang sangat diketik (mis. Int [] departmentIds), dalam hal ini Anda akan baik-baik saja.
Anthony
Solusi luar biasa, @Matt Hamilton. Tidak tahu apakah ini akan membantu siapa pun, tetapi saya mendapatkan hasil yang lebih akurat pada SQL Server 2008r ketika saya mencari bidang teks dengan menggunakan "join dbo.SplitWords (@MyParameterArray) p PADA CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki
3

Salah satu metode yang mungkin ingin Anda pertimbangkan jika Anda akan bekerja dengan banyak nilai adalah dengan menuliskannya ke tabel sementara terlebih dahulu. Maka Anda tinggal bergabung seperti biasa.

Dengan cara ini, Anda hanya mengurai satu kali.

Ini paling mudah untuk menggunakan salah satu UDF 'Pisah', tetapi begitu banyak orang telah memposting contoh-contoh itu, saya pikir saya akan mengambil rute yang berbeda;)

Contoh ini akan membuat tabel sementara untuk Anda ikuti (#tmpDept) dan mengisinya dengan id departemen yang Anda lewati. Saya berasumsi Anda memisahkannya dengan koma, tetapi Anda dapat - tentu saja - mengubah untuk apa pun yang Anda inginkan.

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

Ini memungkinkan Anda untuk melewati satu id departemen, beberapa id dengan koma di antaranya, atau bahkan beberapa id dengan koma dan spasi di antara mereka.

Jadi jika Anda melakukan sesuatu seperti:

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

Anda akan melihat nama-nama semua ID departemen yang Anda lewati ...

Sekali lagi, ini dapat disederhanakan dengan menggunakan fungsi untuk mengisi tabel sementara ... Saya terutama melakukannya tanpa satu hanya untuk membunuh beberapa kebosanan :-P

- Kevin Fairchild

Kevin Fairchild
sumber
3

Anda bisa menggunakan XML.

Misalnya

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) 

Perintah sp_xml_preparedocument sudah ada di dalamnya .

Ini akan menghasilkan output:

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

yang memiliki semua (lebih?) yang Anda butuhkan.

Tidak berbelit-belit
sumber
2

Metode XML super cepat, jika Anda ingin menggunakan prosedur tersimpan dan meneruskan daftar ID Departemen yang dipisahkan koma:

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

Semua kredit diberikan ke Blog Guru Brad Schulz

Nishant
sumber
-3

Coba yang ini:

@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)  +'%')

sangat sederhana.

pengguna1006743
sumber
1
sangat sederhana - dan sangat salah. Tetapi bahkan jika Anda akan memperbaiki masalah dalam kode Anda itu akan sangat lambat. Lihat tautan "Metode Sangat Lambat" di jawaban yang diterima untuk detail.
Sebastian Meine