Definir variável a ser usada com o operador IN (T-SQL)

138

Eu tenho uma consulta Transact-SQL que usa o operador IN. Algo assim:

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

Existe uma maneira de definir uma variável para armazenar toda a lista "(1,2,3,4)"? Como devo defini-lo?

declare @myList {data type}
set @myList = (1,2,3,4)
select * from myTable where myColumn in @myList
Marcos Crispino
fonte
7
Esta pergunta não é a mesma que a pergunta "Parametrizar uma cláusula SQL IN". Esta pergunta se refere ao T-SQL nativo, a outra pergunta se refere ao C #.
Slogmeister Extraordinaire

Respostas:

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)
LukeH
fonte
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)
realPT
fonte
T-SQL diz[Err] 42000 - [SQL Server]Must declare the scalar variable "@mylist".
Cees Timmerman
1
Corrigido para você @Paul #
Stefan Z Camilleri
5
Você pode apenas usar (VALUES (1),(2),(3),(4),(5))diretamente?
toddmo
Esta foi a melhor solução para minhas necessidades. Eu precisava de uma variável como uma lista de IDs que estava obtendo de um Select para que os valores não fossem predeterminados. Isso conseguiu exatamente o que eu precisava. Obrigado!
precisa saber é o seguinte
12

Há duas maneiras de lidar com listas dinâmicas de csv para consultas TSQL:

1) Usando uma seleção interna

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

2) Usando TSQL dinamicamente concatenado

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) Uma terceira opção possível são as variáveis ​​da tabela. Se você possui o SQl Server 2005, pode usar uma variável de tabela. Se você está no Sql Server 2008, pode até passar variáveis ​​de tabela inteira como parâmetro para procedimentos armazenados e usá-lo em uma associação ou como uma subseleção na 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)
hollystyles
fonte
5
@ badbod99 - Isso é uma generalização e todas as generalizações estão erradas :) Eu tenho alternativas oferecidas
hollystyles
1
@Vilx - você quer dizer para definir a variável @list? se assim estiver definido, mas definir apenas uma variável, com select, você poderá preencher várias variáveis ​​em uma instrução. Como não há muito entre eles, tenho o hábito de sempre usar SELECT.
9119 hollystyles
1
Verdade ... muito geral. Sua alternativa é melhor. Eu realmente quero dizer que gerar SQL a partir de um script SQL geralmente causa código não sustentável, risco de ataques de injeção e uma série de outras maldades.
badbod99
9

Use uma função 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;

Em vez de usar like, você faz uma junção interna com a tabela retornada pela função:

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

torna-se

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

Em uma tabela de registro 1M não indexada, a segunda versão demorou cerca de metade do tempo ...

Felicidades

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

Observe que, para sistemas de produção ou lista longa, não é recomendável usar dessa maneira, pois pode ser muito mais lento do que o INoperador simples someColumnName in (1,2,3,4)(testado usando mais de 8000 itens)

Vova
fonte
4

Não, não existe esse tipo. Mas existem algumas opções:

  • Consultas geradas dinamicamente (sp_executesql)
  • Tabelas temporárias
  • Variáveis ​​do tipo tabela (coisa mais próxima que existe de uma lista)
  • Crie uma string XML e converta-a em uma tabela com as funções XML (realmente complicada e rotatória, a menos que você tenha um XML para começar)

Nenhuma delas é realmente elegante, mas é a melhor que existe.

Vilx-
fonte
4

ligeira melhoria no @LukeH, não há necessidade de repetir o "INSERT INTO": e a resposta do @ realPT - não é necessário ter o SELECT:

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

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

fonte
4

Sei que agora é antigo, mas TSQL => 2016, você pode 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)
Nathan Evans
fonte
4

A partir do SQL2017, você pode usar STRING_SPLIT e fazer o seguinte:

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

Se você quiser fazer isso sem usar uma segunda tabela, poderá fazer uma comparação LIKE com um CAST:

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

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

Se o campo que você está comparando já for uma sequência, não será necessário CAST.

Ao redor da correspondência da coluna e de cada valor exclusivo em vírgulas, será garantida uma correspondência exata. Caso contrário, um valor de 1 seria encontrado em uma lista contendo ', 4,2,15,'

Michael Reyes
fonte
1

Como ninguém mencionou antes, a partir do Sql Server 2016 você também pode usar matrizes json e OPENJSON (Transact-SQL):

declare @filter nvarchar(max) = '[1,2]'

select *
from dbo.Test as t
where
    exists (select * from openjson(@filter) as tt where tt.[value] = t.id)

Você pode testá-lo em sql fiddle demo

Você também pode cobrir casos mais complicados com json com mais facilidade - consulte Lista de valores e intervalo de pesquisa no SQL usando a cláusula WHERE IN com variável SQL?

Roman Pekar
fonte
1

Este usa PATINDEX para corresponder os IDs de uma tabela a uma lista inteira delimitada sem dígito.

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

  • ao converter como varchar e não especificar o tamanho de bytes entre parênteses, o comprimento padrão é 30
  • % (curinga) corresponderá a qualquer sequência de zero ou mais caracteres
  • ^ (curinga) para não corresponder
  • [^ 0-9] corresponderá a qualquer caractere que não seja um dígito
  • PATINDEX é uma função padrão SQL que retorna a posição de um padrão em uma string
Marne
fonte
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;
Muhammed Fatih Yıldız
fonte
5
será uma resposta melhor se você descrever seu código lá!
Profundo Kakkar
0

Eu acho que você terá que declarar uma string e depois executar essa string SQL.

Dê uma olhada no sp_executeSQL

BIDeveloper
fonte