Procedura składowana T-SQL, która akceptuje wiele wartości identyfikatorów

145

Czy istnieje wdzięczny sposób obsługi przekazywania listy identyfikatorów jako parametru do procedury składowanej?

Na przykład chcę, aby działy 1, 2, 5, 7, 20 były zwracane przez moją procedurę składowaną. W przeszłości przekazywałem listę identyfikatorów oddzielonych przecinkami, tak jak w poniższym kodzie, ale czuję się naprawdę brudny, robiąc to.

Myślę, że SQL Server 2005 jest moim jedynym ograniczeniem.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)
JasonS
źródło
Oto wariant metody XML, który właśnie znalazłem.
JasonS
5
Jeśli korzystasz z programu SQL Server 2008, możesz użyć parametru z wartościami przechowywanymi w tabeli. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson
To było dla mnie pomocne
Zameer

Odpowiedzi:

237

Erland Sommarskog od 16 lat utrzymuje autorytatywną odpowiedź na to pytanie: tablice i listy w SQL Server .

Istnieje co najmniej tuzin sposobów przekazywania tablicy lub listy do zapytania; każdy ma swoje unikalne zalety i wady.

Naprawdę nie mogę polecić wystarczająco dużo, aby przeczytać artykuł, aby dowiedzieć się o kompromisach między wszystkimi tymi opcjami.

Portman
źródło
11

Tak, Twoje obecne rozwiązanie jest podatne na ataki typu SQL injection.

Najlepszym rozwiązaniem, które znalazłem, jest użycie funkcji, która dzieli tekst na słowa (jest ich kilka zamieszczonych tutaj lub możesz użyć tego z mojego bloga ), a następnie dołączenie tego do swojej tabeli. Coś jak:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Matt Hamilton
źródło
14
Nie jestem pewien, czy jest „podatny na ataki typu SQL injection”, chyba że przechowywany proces jest wywoływany bezpośrednio od niezaufanych klientów, w którym to przypadku masz większe problemy. Kod warstwy usług powinien generować ciąg @DepartmentIds z silnie wpisanych danych (np. Int [] DepartmentIds), w takim przypadku wszystko będzie dobrze.
Anthony
Niesamowite rozwiązanie, @Matt Hamilton. Nie wiem, czy to komukolwiek pomoże, ale otrzymałem dokładniejsze wyniki w SQL Server 2008r, gdy szukałem pól tekstowych za pomocą polecenia „join dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0”
Darkloki
3

Jedną z metod, które warto rozważyć, jeśli zamierzasz dużo pracować z wartościami, jest zapisanie ich najpierw w tabeli tymczasowej. Następnie po prostu dołącz do tego jak zwykle.

W ten sposób parsujesz tylko raz.

Najłatwiej jest użyć jednego z UDF 'Split', ale tak wiele osób zamieściło ich przykłady, pomyślałem, że pójdę inną drogą;)

Ten przykład utworzy tymczasową tabelę, do której możesz dołączyć (#tmpDept) i wypełni ją identyfikatorami działów, które przekazałeś. Zakładam, że oddzielasz je przecinkami, ale możesz - oczywiście - zmienić to co chcesz.

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

Umożliwi to przekazanie jednego identyfikatora działu, wielu identyfikatorów z przecinkami między nimi, a nawet wielu identyfikatorów z przecinkami i spacjami między nimi.

Więc jeśli zrobiłeś coś takiego:

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

Zobaczysz nazwy wszystkich przekazanych identyfikatorów działów ...

Ponownie, można to uprościć, używając funkcji do zapełnienia tabeli tymczasowej ... Zrobiłem to głównie bez niej, aby zabić nudę :-P

- Kevin Fairchild

Kevin Fairchild
źródło
3

Możesz użyć XML.

Na przykład

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) 

Polecenie sp_xml_preparedocument jest wbudowane.

To dałoby wynik:

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

który ma wszystko (więcej?) tego, czego potrzebujesz.

Niepodcięte
źródło
2

Superszybka metoda XML, jeśli chcesz użyć procedury składowanej i przekazać listę identyfikatorów działów oddzielonych przecinkami:

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

Wszystko zasługa bloga Guru Brada Schulza

Nishant
źródło
-3

Spróbuj tego:

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

bardzo prosty.

user1006743
źródło
1
bardzo proste - i bardzo złe. Ale nawet jeśli naprawisz problem w swoim kodzie, będzie to bardzo powolne. Szczegółowe informacje można znaleźć pod linkiem „Naprawdę wolne metody” w zaakceptowanej odpowiedzi.
Sebastian Meine