Хранимая процедура T-SQL, которая принимает несколько значений Id
-
09-06-2019 - |
Вопрос
Есть ли изящный способ обработать передачу списка идентификаторов в качестве параметра хранимой процедуре?
Например, я хочу, чтобы отделы 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)
Решение
Эрланд Соммарског придерживался авторитетного ответа на этот вопрос в течение последних 16 лет: Массивы и списки в SQL Server.
Существует по меньшей мере дюжина способов передать массив или список в запрос;у каждого из них есть свои уникальные плюсы и минусы.
- Параметры с табличным значением.Только SQL Server 2008 и выше и, вероятно, наиболее близкий к универсальному "наилучшему" подходу.
- Итерационный метод.Передайте строку с разделителями и выполните цикл по ней.
- Использование среды CLR.SQL Server 2005 и выше от .Только для языков NET.
- XML.Очень хорош для вставки большого количества строк;может быть излишеством для избранных.
- Таблица чисел.Более высокая производительность / сложность, чем простой итерационный метод.
- Элементы фиксированной длины.Фиксированная длина повышает скорость работы со строкой с разделителями
- Функция чисел.Вариации таблицы чисел и фиксированной длины, где числа генерируются в функции, а не берутся из таблицы.
- Рекурсивное Общее Табличное Выражение (CTE).SQL Server 2005 и выше, по-прежнему не слишком сложный и с более высокой производительностью, чем итерационный метод.
- Динамический SQL.Может быть медленным и иметь последствия для безопасности.
- Передача списка в виде Множество Параметров.Утомительно и подвержено ошибкам, но просто.
- Действительно Медленные Методы.Методы, использующие charindex, patindex или ПОДОБНЫЕ.
Я действительно не могу рекомендовать достаточно, чтобы прочитайте статью чтобы узнать о компромиссах между всеми этими вариантами.
Другие советы
Да, ваше текущее решение подвержено атакам с использованием SQL-инъекций.
Лучшее решение, которое я нашел, - это использовать функцию, которая разбивает текст на слова (здесь опубликовано несколько, или вы можете использовать это из моего блога) а затем присоедините это к своему столу.Что -то вроде:
SELECT d.[Name]
FROM Department d
JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Вы могли бы использовать 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
в котором есть все (больше?) из того, что вам нужно.
Один из методов, который вы, возможно, захотите рассмотреть, если собираетесь часто работать со значениями, - это сначала записать их во временную таблицу.Затем вы просто присоединяетесь к нему, как обычно.
Таким образом, вы выполняете синтаксический анализ только один раз.
Проще всего использовать один из "Разделенных" UDF, но так много людей опубликовали их примеры, что я решил пойти другим путем ;)
В этом примере будет создана временная таблица, к которой вы сможете присоединиться (#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
-- Кевин Фэйрчайлд
Сверхбыстрый 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))
Вся заслуга принадлежит Гуру Блог Брэда Шульца
Попробуй Вот Это:
@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) +'%')
очень просто.