Збережена процедура 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

Ерланд Соммарського зберігає авторитетну відповідь на це питання протягом останніх 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, коли шукав текстові поля, використовуючи "приєднати dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki

3

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

Таким чином, ти розбираєшся лише один раз.

Найпростіше використовувати один з "Спліт" АДС, але так багато людей опублікували приклади таких, я вважав, що я піду іншим маршрутом;)

Цей приклад створить вам тимчасову таблицю для приєднання (#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

що має все (більше?) того, що вам потрібно.


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

Вся заслуга припадає на блог Гуру Бреда Шульца


-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
дуже просто - і дуже неправильно. Але навіть якщо ви вирішите проблему у своєму коді, це буде дуже повільно. Для отримання детальної інформації див. Посилання "Дійсно повільні методи" у прийнятій відповіді.
Себастьян Мейн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.