Procedimiento almacenado de T-SQL que acepta múltiples valores de Id.

145

¿Hay alguna manera elegante de manejar pasar una lista de identificadores como parámetro a un procedimiento almacenado?

Por ejemplo, quiero que los departamentos 1, 2, 5, 7, 20 sean devueltos por mi procedimiento almacenado. En el pasado, pasé una lista de identificadores delimitados por comas, como el código a continuación, pero me siento muy sucio haciéndolo.

SQL Server 2005 es mi única limitación aplicable, creo.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)
JasonS
fuente
Aquí hay una variante del método XML que acabo de encontrar.
JasonS
55
Si está utilizando SQL Server 2008, puede usar un parámetro con valores de tabla. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Respuestas:

237

Erland Sommarskog ha mantenido la respuesta autorizada a esta pregunta durante los últimos 16 años: matrices y listas en SQL Server .

Hay al menos una docena de formas de pasar una matriz o lista a una consulta; cada uno tiene sus propios pros y contras únicos.

Realmente no puedo recomendar lo suficiente para leer el artículo para conocer las compensaciones entre todas estas opciones.

Portman
fuente
11

Sí, su solución actual es propensa a los ataques de inyección SQL.

La mejor solución que he encontrado es usar una función que divide el texto en palabras (hay algunas publicadas aquí, o puedes usar esta de mi blog ) y luego unirlas a tu tabla. Algo como:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
Matt Hamilton
fuente
14
No estoy seguro de que sea "propenso a ataques de inyección SQL" a menos que el proceso almacenado sea invocable directamente desde clientes no confiables, en cuyo caso tiene problemas más grandes. El código de la capa de servicio debe generar la cadena @DepartmentIds a partir de datos fuertemente tipados (por ejemplo, int [] departmentIds), en cuyo caso estará bien.
Anthony
Impresionante solución, @Matt Hamilton. No sé si esto ayudará a alguien, pero obtuve resultados más precisos en SQL Server 2008r cuando buscaba campos de texto usando "join dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki
3

Un método que quizás desee considerar si va a trabajar mucho con los valores es escribirlos primero en una tabla temporal. Entonces solo te unes a él como de costumbre.

De esta manera, solo estás analizando una vez.

Es más fácil usar uno de los UDF 'Split', pero tanta gente ha publicado ejemplos de eso, pensé que iría por una ruta diferente;)

Este ejemplo creará una tabla temporal para que se una (#tmpDept) y la complete con los ID del departamento que ingresó. Supongo que los está separando con comas, pero puede, por supuesto, cambiar a lo que quieras.

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

Esto le permitirá pasar una identificación de departamento, múltiples identificaciones con comas entre ellas, o incluso múltiples identificaciones con comas y espacios entre ellas.

Entonces, si hiciste algo como:

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

Vería los nombres de todas las ID de departamento que pasó ...

Nuevamente, esto se puede simplificar usando una función para llenar la tabla temporal ... Principalmente lo hice sin una solo para matar el aburrimiento :-P

- Kevin Fairchild

Kevin Fairchild
fuente
3

Podrías usar XML.

P.ej

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) 

El comando sp_xml_preparedocument está integrado.

Esto produciría la salida:

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 tiene todo (¿más?) de lo que necesitas.

Sin cortar
fuente
2

Un método XML superrápido, si desea utilizar un procedimiento almacenado y pasar la lista separada por comas de ID 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 el crédito va al blog de Guru Brad Schulz

Nishant
fuente
-3

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

muy simple.

usuario1006743
fuente
1
Muy simple, y muy equivocado. Pero incluso si solucionara el problema en su código, sería muy lento. Consulte el enlace "Métodos realmente lentos" en la respuesta aceptada para obtener más detalles.
Sebastian Meine