Variable SQL para contener la lista de enteros

175

Estoy tratando de depurar los informes SQL de otra persona y he colocado la consulta de informes subyacente en una ventana de consulta de SQL 2012.

Uno de los parámetros que solicita el informe es una lista de enteros. Esto se logra en el informe a través de un cuadro desplegable de selección múltiple. La consulta subyacente del informe utiliza esta lista entera en la wherecláusula, por ejemplo

select *
from TabA
where TabA.ID in (@listOfIDs)

No quiero modificar la consulta que estoy depurando, pero no puedo entender cómo crear una variable en el servidor SQL que pueda contener este tipo de datos para probarla.

p.ej

declare @listOfIDs int
set listOfIDs  = 1,2,3,4

No hay ningún tipo de datos que pueda contener una lista de enteros, entonces, ¿cómo puedo ejecutar la consulta de informe en mi SQL Server con los mismos valores que el informe?

ErickTreetops
fuente
1
Sé que he usado el parámetro de valor de tabla de TVP para insertar datos, pero ahora estoy seguro de si se puede usar en un lugar. ¿Continuación?
paparazzo
2
pregunta bien redactada +1
RayLoveless

Respuestas:

224

Tabla variable

declare @listOfIDs table (id int);
insert @listOfIDs(id) values(1),(2),(3);    

select *
from TabA
where TabA.ID in (select id from @listOfIDs)

o

declare @listOfIDs varchar(1000);
SET @listOfIDs = ',1,2,3,'; --in this solution need put coma on begin and end

select *
from TabA
where charindex(',' + CAST(TabA.ID as nvarchar(20)) + ',', @listOfIDs) > 0
slavoo
fuente
2
Gracias por eso, pero una vez más me obliga a reescribir la forma en que se lee la variable en la consulta. Tengo que mantenerlo igual.
ErickTreetops
3
¿Qué sucede si no sabe cuáles son los ID y eso proviene de una consulta? Ejemplo: SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234)esta consulta devolverá varias ID y recibo un error que dice que la subconsulta devolvió más de un resultado y que no está permitido. ¿Hay alguna forma de crear una variable que almacene una matriz si los ID de una subconsulta?
Rafael Moreira
1
Probé la segunda opción y funciona para menos cantidad de registros. Cuando aumento el número de ID, obtengo el error TimeOut. Tal vez el reparto está obstaculizando la actuación.
Usuario M
No es la respuesta a la pregunta original, pero es la respuesta a una que no pregunté, así que estoy bien. Paso una lista <int> como parámetro, pero quiero hacer una variable local para probar en SSMS. Es asesino
Wade Hatler
Gran respuesta, me ahorró mucho tiempo
Alfredo A.
35

Suponiendo que la variable es algo similar a:

CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)

Y el Procedimiento almacenado lo está utilizando de esta forma:

ALTER Procedure [dbo].[GetFooByIds]
    @Ids [IntList] ReadOnly
As 

Puede crear la IntList y llamar al procedimiento así:

Declare @IDs IntList;
Insert Into @IDs Select Id From dbo.{TableThatHasIds}
Where Id In (111, 222, 333, 444)
Exec [dbo].[GetFooByIds] @IDs

O si proporciona la IntList usted mismo

DECLARE @listOfIDs dbo.IntList
INSERT INTO @listofIDs VALUES (1),(35),(118);
William Mueller
fuente
17

Tiene razón, no hay ningún tipo de datos en SQL-Server que pueda contener una lista de enteros. Pero lo que puede hacer es almacenar una lista de enteros como una cadena.

DECLARE @listOfIDs varchar(8000);
SET @listOfIDs = '1,2,3,4';

Luego puede dividir la cadena en valores enteros separados y ponerlos en una tabla. Su procedimiento ya podría hacer esto.

También puede usar una consulta dinámica para lograr el mismo resultado:

DECLARE @SQL nvarchar(8000);

SET @SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + @listOfIDs + ')';
EXECUTE (@SQL);
Möoz
fuente
Gracias, pero nuevamente necesitaría modificar una consulta que no tengo permitido.
ErickTreetops
2
Si alguien usa esto, tenga en cuenta que esto podría ser muy vulnerable a la inyección de SQL si @listOfIDs es un parámetro de cadena proporcionado por un usuario. Dependiendo de la arquitectura de su aplicación, esto podría o no ser un problema.
Rogala
@Rogala De acuerdo, los usuarios deberán hacer su propio saneamiento según sea necesario.
Möoz
1
@ Möoz Recomiendo agregar una nota a su respuesta para reflejar esto. No todos lo saben, y simplemente copian y pegan soluciones sin pensar en las consecuencias. El SQL dinámico es MUY peligroso, y lo evito así.
Rogala
@ Möoz Además, no voté en contra de su respuesta, pero ¿qué le tomará unos minutos y verifique mi respuesta? La función STRING_SPLIT es bastante dulce, ¡y creo que lo superarás!
Rogala
6

Para SQL Server 2016+ y Azure SQL Database, se agregó la función STRING_SPLIT que sería una solución perfecta para este problema. Aquí está la documentación: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Aquí hay un ejemplo:

/*List of ids in a comma delimited string
  Note: the ') WAITFOR DELAY ''00:00:02''' is a way to verify that your script 
        doesn't allow for SQL injection*/
DECLARE @listOfIds VARCHAR(MAX) = '1,3,a,10.1,) WAITFOR DELAY ''00:00:02''';

--Make sure the temp table was dropped before trying to create it
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;

--Create example reference table
CREATE TABLE #MyTable
([Id] INT NOT NULL);

--Populate the reference table
DECLARE @i INT = 1;
WHILE(@i <= 10)
BEGIN
    INSERT INTO #MyTable
    SELECT @i;

    SET @i = @i + 1;
END

/*Find all the values
  Note: I silently ignore the values that are not integers*/
SELECT t.[Id]
FROM #MyTable as t
    INNER JOIN 
        (SELECT value as [Id] 
        FROM STRING_SPLIT(@listOfIds, ',')
        WHERE ISNUMERIC(value) = 1 /*Make sure it is numeric*/
            AND ROUND(value,0) = value /*Make sure it is an integer*/) as ids
    ON t.[Id] = ids.[Id];

--Clean-up
DROP TABLE #MyTable;

El resultado de la consulta es 1,3

~ Saludos

Rogala
fuente
4

Al final llegué a la conclusión de que sin modificar cómo funciona la consulta no podría almacenar los valores en variables. Utilicé el generador de perfiles SQL para capturar los valores y luego los codifiqué en la consulta para ver cómo funcionaba. Había 18 de estos arreglos enteros y algunos tenían más de 30 elementos en ellos.

Creo que MS / SQL necesita introducir algunos tipos de datos adicionales en el lenguaje. Las matrices son bastante comunes y no veo por qué no podría usarlas en un proceso almacenado.

ErickTreetops
fuente
66
SQL Server no necesita matrices, cuando tiene parámetros y variables con valores de tabla.
John Saunders
1
Entonces, lo que sabemos es que la consulta usa una lista de enteros (¿que le pasó una matriz?). Lo que no entiendo es cómo su consulta los estaba utilizando sin utilizar uno de los métodos dados en las respuestas. Proporcione un poco más de contexto y podemos ayudarlo más.
Möoz
2

No puede hacerlo así, pero puede ejecutar toda la consulta almacenándola en una variable.

Por ejemplo:

DECLARE @listOfIDs NVARCHAR(MAX) = 
    '1,2,3'

DECLARE @query NVARCHAR(MAX) = 
    'Select *
     From TabA
     Where TabA.ID in (' + @listOfIDs + ')'

Exec (@query)
thepirat000
fuente
2
Como se indicó en un comentario anterior, dependiendo de cómo implemente este tipo de solución, tenga en cuenta que esto podría ser vulnerable a la inyección de SQL si @listOfIDs es un parámetro proporcionado por un usuario.
Rogala
2

Hay una nueva función en SQL llamada string_splitsi está utilizando la lista de cadenas. Enlace de referencia STRING_SPLIT (Transact-SQL)

DECLARE @tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(@tags, ',')
WHERE RTRIM(value) <> '';

puede pasar esta consulta de la insiguiente manera:

SELECT *
  FROM [dbo].[yourTable]
  WHERE (strval IN (SELECT value FROM STRING_SPLIT(@tags, ',') WHERE RTRIM(value) <> ''))
Ravi Anand
fuente
1
Parece plausible pero desafortunadamente solo SQL Server 2016 en adelante.
AnotherFineMess
0

Yo uso esto :

1-Declare una variable de tabla temporal en el script de su edificio:

DECLARE @ShiftPeriodList TABLE(id INT NOT NULL);

2-Asignar a la tabla temporal:

IF (SOME CONDITION) 
BEGIN 
        INSERT INTO @ShiftPeriodList SELECT ShiftId FROM [hr].[tbl_WorkShift]
END
IF (SOME CONDITION2)
BEGIN
    INSERT INTO @ShiftPeriodList
        SELECT  ws.ShiftId
        FROM [hr].[tbl_WorkShift] ws
        WHERE ws.WorkShift = 'Weekend(VSD)' OR ws.WorkShift = 'Weekend(SDL)'

END

3-Consulte la tabla cuando la necesite en una declaración WHERE:

INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM @ShiftPeriodList)
Max Alexander Hanna
fuente