Procedimiento almacenado T-SQL que acepta múltiples valores de Id.
-
09-06-2019 - |
Pregunta
¿Existe una manera elegante de manejar el paso de una lista de identificadores como parámetro a un procedimiento almacenado?
Por ejemplo, quiero que mi procedimiento almacenado devuelva los departamentos 1, 2, 5, 7, 20.En el pasado, pasé una lista de identificadores delimitados por comas, como el siguiente código, pero me siento muy sucio al hacerlo.
Creo que SQL Server 2005 es mi única limitación aplicable.
create procedure getDepartments
@DepartmentIds varchar(max)
as
declare @Sql varchar(max)
select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
exec(@Sql)
Solución
Erland Sommarskog mantiene desde hace 16 años la respuesta autorizada a esta pregunta: 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.
- Parámetros con valores de tabla.SQL Server 2008 y versiones posteriores únicamente, y probablemente el más cercano a un "mejor" enfoque universal.
- El método iterativo.Pase una cadena delimitada y recorrala.
- Usando el CLR.SQL Server 2005 y superior únicamente desde lenguajes .NET.
- XML.Muy bueno para insertar muchas filas;puede ser excesivo para SELECT.
- Tabla de números.Mayor rendimiento/complejidad que el método iterativo simple.
- Elementos de longitud fija.La longitud fija mejora la velocidad sobre la cadena delimitada
- Función de los números.Variaciones de la tabla de números y longitud fija donde los números se generan en una función en lugar de tomarse de una tabla.
- Expresión de tabla común recursiva (CTE).SQL Server 2005 y superior, aún no es demasiado complejo y tiene mayor rendimiento que el método iterativo.
- SQL dinámico.Puede ser lento y tiene implicaciones de seguridad.
- Pasando la lista como Muchos parámetros.Tedioso y propenso a errores, pero sencillo.
- Métodos realmente lentos.Métodos que utilizan charindex, patindex o LIKE.
Realmente no puedo recomendar lo suficiente para leer el artículo para conocer las ventajas y desventajas entre todas estas opciones.
Otros consejos
Sí, su solución actual es propensa a 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 este de mi blog) y luego únelo a tu mesa.Algo como:
SELECT d.[Name]
FROM Department d
JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId
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?) lo que necesitas.
Un método que quizás quieras considerar si vas a trabajar mucho con los valores es escribirlos primero en una tabla temporal.Luego simplemente únete como de costumbre.
De esta manera, solo estarás analizando una vez.
Es más fácil usar una de las UDF 'divididas', pero tanta gente ha publicado ejemplos de ellas que pensé en tomar una ruta diferente;)
Este ejemplo creará una tabla temporal a la que puede unirse (#tmpDept) y la completará con la identificación del departamento que ingresó.Supongo que los estás separando con comas, pero puedes, por supuesto, cambiarlo 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, varias identificaciones con comas entre ellas o incluso varias identificaciones con comas y espacios entre ellas.
Entonces, si hicieras algo como:
SELECT Dept.Name
FROM Departments
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name
Verá los nombres de todos los ID de departamento que pasó...
Nuevamente, esto se puede simplificar usando una función para llenar la tabla temporal...Principalmente lo hice sin uno solo para matar el aburrimiento :-P
--Kevin Fairchild
Un método XML ultrarrápido, si desea utilizar un procedimiento almacenado y pasar la lista de ID de departamento separados por comas:
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 es para Guru. Blog de Brad Schulz
Pruebe 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.