Хранимая процедура 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)
Jasons
источник
Вот вариант метода XML, который я только что нашел.
JasonS
5
Если вы используете SQL Server 2008, вы можете использовать табличный параметр. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ян Нельсон
Это было полезно для меня: sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
Zameer

Ответы:

237

Erland Sommarskog поддерживает авторитетный ответ на этот вопрос в течение последних 16 лет: массивы и списки в SQL Server .

Существует как минимум дюжина способов передать массив или список в запрос; у каждого свои уникальные плюсы и минусы.

Я действительно не могу рекомендовать достаточно прочитать статью, чтобы узнать о компромиссах между всеми этими вариантами.

Портман
источник
11

Да, ваше текущее решение подвержено атакам SQL-инъекций.

Лучшее решение, которое я нашел, - это использовать функцию, которая разбивает текст на слова (их несколько опубликовано здесь, или вы можете использовать это из моего блога ), а затем присоедините их к своей таблице. Что-то вроде:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Мэтт Гамильтон
источник
14
Я не уверен, что он «подвержен атакам с использованием SQL-инъекций», если только хранимый процесс не вызывается напрямую от ненадежных клиентов, и в этом случае у вас возникают большие проблемы. Код уровня обслуживания должен генерировать строку @DepartmentIds из строго типизированных данных (например, int [] DepartmentIds), в этом случае все будет в порядке.
Энтони
Отличное решение, @Matt Hamilton. Не знаю, поможет ли это кому-нибудь, но я получил более точные результаты на SQL Server 2008r, когда искал текстовые поля с помощью «join dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0»
Darkloki
3

Один метод, который вы можете рассмотреть, если вы собираетесь много работать со значениями, - это сначала записать их во временную таблицу. Тогда вы просто присоединитесь к нему, как обычно.

Таким образом, вы анализируете только один раз.

Проще всего использовать один из UDF-файлов «Split», но так много людей опубликовали примеры, и я подумал, что пойду другим путем;)

В этом примере будет создана временная таблица для присоединения к ней (#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

Это позволит вам передавать один идентификатор отдела, несколько идентификаторов с запятыми между ними или даже несколько идентификаторов с запятыми и пробелами между ними.

Так что, если вы сделали что-то вроде:

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

Вы увидите имена всех идентификаторов отделов, которые вы передали ...

Опять же, это можно упростить, используя функцию для заполнения временной таблицы ... В основном я делал это без единой цели, чтобы просто убить скуку :-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

который имеет все (больше?) того, что вам нужно.

Unsliced
источник
2

Сверхбыстрый метод XML, если вы хотите использовать хранимую процедуру и передать список идентификаторов отделов через запятую:

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

Вся заслуга в блоге Гуру Брэда Шульца

Nishant
источник
-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)  +'%')

очень просто.

user1006743
источник
1
очень просто - и очень неправильно. Но даже если вы решите проблему в своем коде, это будет очень медленно. Подробнее см. Ссылку «Действительно медленные методы» в принятом ответе.
Себастьян Майн