Procedimento armazenado T-SQL que aceita vários valores de ID

145

Existe uma maneira elegante de lidar com a passagem de uma lista de IDs como parâmetro para um procedimento armazenado?

Por exemplo, quero os departamentos 1, 2, 5, 7, 20 retornados pelo meu procedimento armazenado. No passado, eu passei uma lista de IDs delimitadas por vírgulas, como o código abaixo, mas me sinto muito sujo fazendo isso.

O SQL Server 2005 é minha única limitação aplicável, eu acho.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)
JasonS
fonte
Aqui está uma variante do método XML que acabei de encontrar.
jasons
5
Se você estiver no SQL Server 2008, poderá usar um parâmetro com valor de tabela. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Respostas:

237

Erland Sommarskog manteve a resposta oficial para esta pergunta nos últimos 16 anos: Matrizes e listas no SQL Server .

Há pelo menos uma dúzia de maneiras de passar uma matriz ou lista para uma consulta; cada um tem seus próprios prós e contras.

Realmente não posso recomendar o suficiente para ler o artigo e aprender sobre as vantagens e desvantagens de todas essas opções.

Portman
fonte
11

Sim, sua solução atual é propensa a ataques de injeção de SQL.

A melhor solução que eu encontrei é usar uma função que divide o texto em palavras (existem algumas postadas aqui, ou você pode usar essa no meu blog ) e depois juntar isso à sua mesa. Algo como:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Matt Hamilton
fonte
14
Não tenho certeza de que seja "propenso a ataques de injeção de SQL", a menos que o processo armazenado possa ser chamado diretamente de clientes não confiáveis; nesse caso, você terá problemas maiores. O código da camada de serviço deve gerar a sequência @DepartmentIds a partir de dados fortemente tipados (por exemplo, int [] departmentIds), caso em que você ficará bem.
Anthony
Solução incrível, @Matt Hamilton. Não sei se isso vai ajudar alguém, mas obtive resultados mais precisos no SQL Server 2008r quando pesquisava campos de texto usando "ingressar no dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki
3

Um método que você pode considerar se estiver trabalhando muito com os valores é gravá-los em uma tabela temporária primeiro. Então você se junta a ele normalmente.

Dessa forma, você está analisando apenas uma vez.

É mais fácil usar uma das UDFs 'Divididas', mas muitas pessoas postaram exemplos delas; achei que eu iria por um caminho diferente;)

Este exemplo criará uma tabela temporária para você participar (#tmpDept) e a preencherá com os IDs de departamento que você inseriu. Suponho que você os esteja separando por vírgulas, mas é possível - é claro - alterar para o que você quiser.

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

Isso permitirá que você passe um ID de departamento, vários IDs com vírgulas entre eles ou mesmo vários IDs com vírgulas e espaços entre eles.

Então, se você fez algo como:

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

Você veria os nomes de todos os IDs de departamento pelos quais passou ...

Novamente, isso pode ser simplificado usando uma função para preencher a tabela temporária ... Eu fiz principalmente sem uma só para matar o tédio :-P

- Kevin Fairchild

Kevin Fairchild
fonte
3

Você poderia usar XML.

Por exemplo

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) 

O comando sp_xml_preparedocument está embutido.

Isso produziria a saída:

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

que tem tudo (mais?) do que você precisa.

Sem fatias
fonte
2

Um método XML super rápido, se você quiser usar um procedimento armazenado e passar a lista separada por vírgula de IDs de Departamento:

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

Todo o crédito vai para o Blog do Guru Brad Schulz

Nishant
fonte
-3

Tente este:

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

muito simples.

user1006743
fonte
1
muito simples - e muito errado. Mas mesmo que você corrija o problema no seu código, isso será muito lento. Veja o link "Métodos realmente lentos" na resposta aceita para obter detalhes.
Sebastian Meine