Definir variable para usar con el operador IN (T-SQL)


138

Tengo una consulta Transact-SQL que usa el operador IN. Algo como esto:

select * from myTable where myColumn in (1,2,3,4)

¿Hay alguna manera de definir una variable para contener toda la lista "(1,2,3,4)"? ¿Cómo debo definirlo?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList

77
Esta pregunta no es lo mismo que la pregunta "Parametrizar una cláusula SQL IN". Esta pregunta se refiere a T-SQL nativo, la otra pregunta se refiere a C #.
Slogmeister Extraordinaire

Respuestas:


113
DECLARE @MyList TABLE (Value INT)
INSERT INTO @MyList VALUES (1)
INSERT INTO @MyList VALUES (2)
INSERT INTO @MyList VALUES (3)
INSERT INTO @MyList VALUES (4)

SELECT *
FROM MyTable
WHERE MyColumn IN (SELECT Value FROM @MyList)

47
DECLARE @mylist TABLE (Id int)
INSERT INTO @mylist
SELECT id FROM (VALUES (1),(2),(3),(4),(5)) AS tbl(id)

SELECT * FROM Mytable WHERE theColumn IN (select id from @mylist)

T-SQL dice[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Cees Timmerman

1
Lo arregló para ti @Paul
Stefan Z Camilleri

55
¿Puedes usar (VALUES (1),(2),(3),(4),(5))directamente?
toddmo

Esta fue la mejor solución para mis necesidades. Necesitaba una variable como una lista de ID que estaba obteniendo de un Select para que los valores no estuvieran predeterminados. Esto logró exactamente lo que necesitaba. ¡Gracias!
Lexi847942

12

Hay dos formas de abordar las listas de csv dinámicas para consultas TSQL:

1) Usando una selección interna

SELECT * FROM myTable WHERE myColumn in (SELECT id FROM myIdTable WHERE id > 10)

2) Uso de TSQL concatenado dinámicamente

DECLARE @sql varchar(max)  
declare @list varchar(256)  
select @list = '1,2,3'  
SELECT @sql = 'SELECT * FROM myTable WHERE myColumn in (' + @list + ')'

exec sp_executeSQL @sql

3) Una posible tercera opción son las variables de tabla. Si tiene SQl Server 2005, puede usar una variable de tabla. Si está en Sql Server 2008, incluso puede pasar variables de tabla completas como un parámetro a los procedimientos almacenados y usarlo en una combinación o como una subselección en la cláusula IN.

DECLARE @list TABLE (Id INT)

INSERT INTO @list(Id)
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4


SELECT
    * 
FROM 
    myTable
    JOIN @list l ON myTable.myColumn = l.Id

SELECT
    * 
FROM 
    myTable
WHERE
    myColumn IN (SELECT Id FROM @list)

55
@ badbod99 - Eso es una generalización y todas las generalizaciones están mal :) He ofrecido alternativas
hollystyles

1
@Vilx: ¿quieres decir para configurar la variable @list? si está establecido, está bien pero solo establece una variable, con select puede completar varias variables en una declaración. Como no hay mucho entre ellos, suelo usar siempre SELECT.
hollystyles

1
Cierto ... muy general. Tu alternativa es mejor. Realmente quiero decir que generar SQL desde una secuencia de comandos SQL generalmente causa código que no se puede mantener, riesgo de ataques de inyección y una serie de otras cosas desagradables.
badbod99

9

Use una función como esta:

CREATE function [dbo].[list_to_table] (@list varchar(4000))
returns @tab table (item varchar(100))
begin

if CHARINDEX(',',@list) = 0 or CHARINDEX(',',@list) is null
begin
    insert into @tab (item) values (@list);
    return;
end


declare @c_pos int;
declare @n_pos int;
declare @l_pos int;

set @c_pos = 0;
set @n_pos = CHARINDEX(',',@list,@c_pos);

while @n_pos > 0
begin
    insert into @tab (item) values (SUBSTRING(@list,@c_pos+1,@n_pos - @c_pos-1));
    set @c_pos = @n_pos;
    set @l_pos = @n_pos;
    set @n_pos = CHARINDEX(',',@list,@c_pos+1);
end;

insert into @tab (item) values (SUBSTRING(@list,@l_pos+1,4000));

return;
end;

En lugar de usar like, realiza una unión interna con la tabla devuelta por la función:

select * from table_1 where id in ('a','b','c')

se convierte

select * from table_1 a inner join [dbo].[list_to_table] ('a,b,c') b on (a.id = b.item)

En una tabla de registro 1M no indexada, la segunda versión tardó aproximadamente la mitad del tiempo ...

salud


5
DECLARE @myList TABLE (Id BIGINT) INSERT INTO @myList(Id) VALUES (1),(2),(3),(4);
select * from myTable where myColumn in(select Id from @myList)

Tenga en cuenta que para una lista larga o sistemas de producción no se recomienda usar de esta manera, ya que puede ser mucho más lento que el simple INoperador someColumnName in (1,2,3,4)(probado con una lista de más de 8000 artículos)


4

No, no existe tal tipo. Pero hay algunas opciones:

  • Consultas generadas dinámicamente (sp_executesql)
  • Mesas temporales
  • Variables de tipo tabla (lo más parecido a una lista)
  • Cree una cadena XML y luego conviértala en una tabla con las funciones XML (realmente incómodo y rotundo, a menos que tenga un XML para comenzar)

Ninguno de estos es realmente elegante, pero eso es lo mejor que hay.


4

ligera mejora en @LukeH, no hay necesidad de repetir "INSERT INTO": y la respuesta de @ realPT - no es necesario tener SELECT:

DECLARE @MyList TABLE (Value INT) 
INSERT INTO @MyList VALUES (1),(2),(3),(4)

SELECT * FROM MyTable
WHERE MyColumn IN (SELECT Value FROM @MyList)

4

Sé que esto es antiguo ahora, pero TSQL => 2016, puede usar STRING_SPLIT:

DECLARE @InList varchar(255) = 'This;Is;My;List';

WITH InList (Item) AS (
    SELECT value FROM STRING_SPLIT(@InList, ';')
)

SELECT * 
FROM [Table]
WHERE [Item] IN (SELECT Tag FROM InList)

4

A partir de SQL2017, puede usar STRING_SPLIT y hacer esto:

declare @myList nvarchar(MAX)
set @myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(@myList,','))

2

Si desea hacer esto sin usar una segunda tabla, puede hacer una comparación LIKE con un CAST:

DECLARE @myList varchar(15)
SET @myList = ',1,2,3,4,'

SELECT *
FROM myTable
WHERE @myList LIKE '%,' + CAST(myColumn AS varchar(15)) + ',%'

Si el campo que está comparando ya es una cadena, no necesitará CAST.

Rodeando tanto la coincidencia de columna como cada valor único en comas asegurará una coincidencia exacta. De lo contrario, un valor de 1 se encontraría en una lista que contiene ', 4,2,15,'



1

Éste usa PATINDEX para hacer coincidir los identificadores de una tabla con una lista de enteros delimitados sin dígitos.

-- Given a string @myList containing character delimited integers 
-- (supports any non digit delimiter)
DECLARE @myList VARCHAR(MAX) = '1,2,3,4,42'

SELECT * FROM [MyTable]
    WHERE 
        -- When the Id is at the leftmost position 
        -- (nothing to its left and anything to its right after a non digit char) 
        PATINDEX(CAST([Id] AS VARCHAR)+'[^0-9]%', @myList)>0 
        OR
        -- When the Id is at the rightmost position
        -- (anything to its left before a non digit char and nothing to its right) 
        PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR), @myList)>0
        OR
        -- When the Id is between two delimiters 
        -- (anything to its left and right after two non digit chars)
        PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR)+'[^0-9]%', @myList)>0
        OR
        -- When the Id is equal to the list
        -- (if there is only one Id in the list)
        CAST([Id] AS VARCHAR)=@myList

Notas:

  • cuando se convierte como varchar y no se especifica el tamaño del byte entre paréntesis, la longitud predeterminada es 30
  • % (comodín) coincidirá con cualquier cadena de cero o más caracteres
  • ^ (comodín) no coincide
  • [^ 0-9] coincidirá con cualquier carácter que no sea un dígito
  • PATINDEX es una función estándar de SQL que devuelve la posición de un patrón en una cadena

0
DECLARE @StatusList varchar(MAX);
SET @StatusList='1,2,3,4';
DECLARE @Status SYS_INTEGERS;
INSERT INTO  @Status 
SELECT Value 
FROM dbo.SYS_SPLITTOINTEGERS_FN(@StatusList, ',');
SELECT Value From @Status;

55
¡será una mejor respuesta si describe su código allí!
Deep Kakkar

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.