T-SQL กระบวนงานที่เก็บไว้ซึ่งยอมรับค่า Id หลายรายการ


145

มีวิธีที่สง่างามในการจัดการผ่านรายการรหัสเป็นพารามิเตอร์ไปยังขั้นตอนการจัดเก็บหรือไม่?

ตัวอย่างเช่นฉันต้องการแผนก 1, 2, 5, 7, 20 ส่งคืนโดยขั้นตอนการจัดเก็บของฉัน ในอดีตฉันได้ผ่านรายการรหัสที่คั่นด้วยจุลภาคเช่นรหัสด้านล่าง แต่รู้สึกสกปรกจริงๆ

SQL Server 2005 เป็นข้อ จำกัด เดียวที่ฉันมี

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

นี่คือตัวแปรของวิธี XML ที่ฉันเพิ่งพบ
JasonS

5
หากคุณใช้ SQL Server 2008 คุณสามารถใช้พารามิเตอร์ที่มีค่าเป็นตาราง http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

สิ่งนี้มีประโยชน์สำหรับฉัน: sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
Zameer

คำตอบ:


237

Erland Sommarskog ยังคงเป็นคำตอบที่เผด็จการคำถามนี้สำหรับที่ผ่านมา 16 ปี: อาร์เรย์และรายการใน SQL Server

มีอย่างน้อยหนึ่งโหลที่จะผ่านอาร์เรย์หรือรายการไปยังแบบสอบถาม แต่ละคนมีข้อดีและข้อเสียที่เป็นเอกลักษณ์ของตนเอง

  • ตารางมูลค่าพารามิเตอร์ SQL Server 2008 และสูงกว่าเท่านั้นและอาจใกล้เคียงกับแนวทาง "ดีที่สุด" ที่เป็นสากล
  • วิธีการทำซ้ำ ผ่านสตริงที่มีการคั่นและวนซ้ำ
  • โดยใช้การ CLR SQL Server 2005 และสูงกว่าจาก. NET ภาษาเท่านั้น
  • XML ดีมากสำหรับการแทรกหลายแถว อาจ overkill สำหรับ SELECT
  • ตารางของตัวเลข ประสิทธิภาพ / ความซับซ้อนสูงกว่าวิธีการวนซ้ำอย่างง่าย
  • องค์ประกอบความยาวคงที่ ความยาวคงที่ช่วยเพิ่มความเร็วในสายอักขระที่มีตัวคั่น
  • ฟังก์ชั่นของตัวเลข ชุดรูปแบบของตารางตัวเลขและความยาวคงที่ซึ่งหมายเลขถูกสร้างในฟังก์ชันแทนที่จะนำมาจากตาราง
  • Recursive Common Table Expression (CTE) SQL Server 2005 และสูงกว่ายังไม่ซับซ้อนเกินไปและมีประสิทธิภาพสูงกว่าวิธีวนซ้ำ
  • SQL แบบไดนามิก อาจช้าและมีผลกระทบด้านความปลอดภัย
  • ผ่านรายการเป็นพารามิเตอร์มาก น่าเบื่อและผิดพลาดง่าย แต่ง่าย
  • วิธีการช้ามาก วิธีการที่ใช้ charindex, patindex หรือ LIKE

ฉันไม่สามารถแนะนำให้อ่านบทความเพื่อเรียนรู้เกี่ยวกับการแลกเปลี่ยนระหว่างตัวเลือกเหล่านี้ทั้งหมดได้


11

ใช่ทางออกปัจจุบันของคุณมีแนวโน้มที่จะโจมตีการฉีด SQL

ทางออกที่ดีที่สุดที่ฉันพบคือใช้ฟังก์ชั่นที่แยกข้อความเป็นคำ (มีโพสต์ไม่กี่ที่นี่หรือคุณสามารถใช้อันนี้จากบล็อกของฉัน ) จากนั้นเข้าร่วมที่โต๊ะของคุณ สิ่งที่ต้องการ:

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

14
ฉันไม่แน่ใจว่าเป็น "แนวโน้มที่จะโจมตีการฉีด SQL" เว้นแต่ proc ที่เก็บไว้สามารถเรียกได้โดยตรงจากลูกค้าที่ไม่น่าเชื่อถือซึ่งในกรณีนี้คุณมีปัญหาที่ใหญ่กว่า โค้ดเลเยอร์บริการควรสร้างสตริง @DepartmentIds จากข้อมูลที่พิมพ์อย่างรุนแรง (เช่น int [] departmentIds) ซึ่งในกรณีนี้คุณจะไม่เป็นไร
Anthony

โซลูชั่นที่ยอดเยี่ยม @ Matt Hamilton ไม่ทราบว่าจะช่วยให้ใคร แต่ฉันได้ผลลัพธ์ที่แม่นยำมากขึ้นใน SQL Server 2008r เมื่อฉันค้นหาช่องข้อความโดยใช้ "เข้าร่วม dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki

3

วิธีหนึ่งที่คุณอาจต้องการพิจารณาว่าคุณจะต้องทำงานกับค่ามากหรือไม่นั้นคือการเขียนลงในตารางชั่วคราวก่อน จากนั้นคุณก็เข้าร่วมมันเหมือนปกติ

ด้วยวิธีนี้คุณจะแยกวิเคราะห์ได้เพียงครั้งเดียว

มันง่ายที่สุดในการใช้หนึ่งใน 'แยก' UDFs แต่ผู้คนมากมายได้โพสต์ตัวอย่างของพวกนั้นฉันคิดว่าฉันจะไปเส้นทางอื่น;)

ตัวอย่างนี้จะสร้างตารางชั่วคราวเพื่อให้คุณเข้าร่วมใน (#tmpDept) และกรอกข้อมูลด้วยรหัสแผนกที่คุณส่งผ่านฉันคิดว่าคุณกำลังแยกพวกเขาด้วยเครื่องหมายจุลภาค แต่แน่นอนคุณสามารถเปลี่ยนได้ ทุกสิ่งที่คุณต้องการ

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

วิธีนี้จะช่วยให้คุณสามารถส่งรหัสแผนกเดียวได้หลาย ID พร้อมด้วยเครื่องหมายจุลภาคคั่นระหว่างพวกเขาหรือแม้กระทั่งหลาย ID ด้วยเครื่องหมายจุลภาคและเว้นวรรคระหว่างพวกเขา

ดังนั้นถ้าคุณทำสิ่งที่ชอบ:

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

คุณจะเห็นชื่อของ ID แผนกทั้งหมดที่คุณส่งผ่านใน ...

อีกครั้งสิ่งนี้สามารถทำให้ง่ายขึ้นได้โดยใช้ฟังก์ชั่นเพื่อเติมตารางชั่วคราว ... ฉันทำมันเป็นหลักโดยไม่มีใครฆ่าความเบื่อ :-P

- เควินแฟร์ไชลด์


3

คุณสามารถใช้ XML

เช่น

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ถูกสร้างขึ้น

สิ่งนี้จะสร้างผลลัพธ์:

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

สิ่งใดที่คุณต้องการ


2

วิธี XML ที่เร็วสุดหากคุณต้องการใช้ขั้นตอนการจัดเก็บและส่งรายการ ID แผนกที่คั่นด้วยเครื่องหมายจุลภาค:

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

เครดิตทั้งหมดไปที่บล็อกของ Guru Brad Schulz


-3

ลองอันนี้:

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

ง่ายมาก.


1
ง่ายมาก - และผิดมาก แต่แม้ว่าคุณจะแก้ไขปัญหาในรหัสของคุณมันจะช้ามาก ดูลิงก์ "วิธีการช้าจริง ๆ " ในคำตอบที่ยอมรับเพื่อดูรายละเอียด
เซบาสเตียนมีน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.