Gespeicherte T-SQL-Prozedur, die mehrere ID-Werte akzeptiert

145

Gibt es eine angemessene Möglichkeit, eine Liste von IDs als Parameter an eine gespeicherte Prozedur zu übergeben?

Zum Beispiel möchte ich, dass die Abteilungen 1, 2, 5, 7, 20 von meiner gespeicherten Prozedur zurückgegeben werden. In der Vergangenheit habe ich eine durch Kommas getrennte Liste von IDs wie den folgenden Code übergeben, aber ich fühle mich dabei wirklich schmutzig.

SQL Server 2005 ist meiner Meinung nach meine einzige anwendbare Einschränkung.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)
JasonS
quelle
Hier ist eine Variante der XML-Methode, die ich gerade gefunden habe.
JasonS
5
Wenn Sie mit SQL Server 2008 arbeiten, können Sie einen tabellenwertigen Parameter verwenden. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Antworten:

237

Erland Sommarskog hat die maßgebliche Antwort auf diese Frage in den letzten 16 Jahren beibehalten: Arrays und Listen in SQL Server .

Es gibt mindestens ein Dutzend Möglichkeiten, ein Array oder eine Liste an eine Abfrage zu übergeben. Jeder hat seine eigenen Vor- und Nachteile.

  • Tabellenwertparameter . Nur SQL Server 2008 und höher und wahrscheinlich am nächsten an einem universellen "besten" Ansatz.
  • Die iterative Methode . Übergeben Sie eine begrenzte Zeichenfolge und durchlaufen Sie sie.
  • Verwenden der CLR . SQL Server 2005 und höher nur aus .NET-Sprachen.
  • XML . Sehr gut zum Einfügen vieler Zeilen; kann für SELECTs übertrieben sein.
  • Zahlentabelle . Höhere Leistung / Komplexität als einfache iterative Methode.
  • Elemente mit fester Länge . Die feste Länge verbessert die Geschwindigkeit gegenüber der begrenzten Zeichenfolge
  • Funktion von Zahlen . Variationen der Zahlentabelle und der festen Länge, bei denen die Zahl in einer Funktion generiert und nicht aus einer Tabelle entnommen wird.
  • Rekursiver Common Table Expression (CTE). SQL Server 2005 und höher, immer noch nicht zu komplex und leistungsfähiger als die iterative Methode.
  • Dynamisches SQL . Kann langsam sein und hat Auswirkungen auf die Sicherheit.
  • Übergeben der Liste als viele Parameter . Mühsam und fehleranfällig, aber einfach.
  • Wirklich langsame Methoden . Methoden, die charindex, patindex oder LIKE verwenden.

Ich kann wirklich nicht genug empfehlen, um den Artikel zu lesen und mehr über die Kompromisse zwischen all diesen Optionen zu erfahren.

Portman
quelle
11

Ja, Ihre aktuelle Lösung ist anfällig für SQL-Injection-Angriffe.

Die beste Lösung, die ich gefunden habe, besteht darin, eine Funktion zu verwenden, die Text in Wörter aufteilt (es gibt einige hier, oder Sie können diese aus meinem Blog verwenden ) und diese dann mit Ihrer Tabelle zu verbinden. Etwas wie:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Matt Hamilton
quelle
14
Ich bin nicht sicher, ob es "anfällig für SQL-Injection-Angriffe" ist, es sei denn, der gespeicherte Prozess kann direkt von nicht vertrauenswürdigen Clients aufgerufen werden. In diesem Fall haben Sie größere Probleme. Der Service-Layer-Code sollte die Zeichenfolge @DepartmentIds aus stark typisierten Daten (z. B. int [] departmentIds) generieren. In diesem Fall ist alles in Ordnung.
Anthony
Tolle Lösung, @Matt Hamilton. Ich weiß nicht, ob dies jemandem helfen wird, aber ich habe genauere Ergebnisse unter SQL Server 2008r erhalten, als ich Textfelder mit "join dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0" durchsucht habe.
Darkloki
3

Eine Methode, die Sie in Betracht ziehen sollten, wenn Sie häufig mit den Werten arbeiten, besteht darin, sie zuerst in eine temporäre Tabelle zu schreiben. Dann machst du einfach wie gewohnt mit.

Auf diese Weise analysieren Sie nur einmal.

Es ist am einfachsten, eine der 'Split'-UDFs zu verwenden, aber so viele Leute haben Beispiele dafür veröffentlicht, dass ich dachte, ich würde einen anderen Weg gehen;)

In diesem Beispiel wird eine temporäre Tabelle erstellt, an der Sie teilnehmen können (#tmpDept), und diese mit den von Ihnen übergebenen Abteilungs-IDs gefüllt. Ich gehe davon aus, dass Sie sie durch Kommas trennen, aber Sie können sie natürlich ändern es zu was auch immer Sie wollen.

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

Auf diese Weise können Sie eine Abteilungs-ID, mehrere IDs mit Kommas dazwischen oder sogar mehrere IDs mit Kommas und Leerzeichen dazwischen übergeben.

Also, wenn Sie so etwas gemacht haben:

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

Sie sehen die Namen aller Abteilungs-IDs, die Sie übergeben haben ...

Auch dies kann vereinfacht werden, indem eine Funktion zum Auffüllen der temporären Tabelle verwendet wird ... Ich habe es hauptsächlich ohne eine getan, nur um Langeweile zu vertreiben :-P

- Kevin Fairchild

Kevin Fairchild
quelle
3

Sie könnten XML verwenden.

Z.B

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) 

Der Befehl sp_xml_preparedocument ist integriert.

Dies würde die Ausgabe erzeugen:

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

Welches hat alles (mehr?) von dem, was Sie brauchen.

Ungeschnitten
quelle
2

Eine superschnelle XML-Methode, wenn Sie eine gespeicherte Prozedur verwenden und die durch Kommas getrennte Liste der Abteilungs-IDs übergeben möchten:

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

Alle Kredite gehen an Guru Brad Schulz 'Blog

Nishant
quelle
-3

Probier diese:

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

sehr einfach.

user1006743
quelle
1
sehr einfach - und sehr falsch. Aber selbst wenn Sie das Problem in Ihrem Code beheben würden, wäre es sehr langsam. Weitere Informationen finden Sie unter dem Link "Wirklich langsame Methoden" in der akzeptierten Antwort.
Sebastian Meine