Variável SQL para manter a lista de números inteiros


175

Estou tentando depurar os relatórios SQL de outra pessoa e coloquei a consulta de relatórios subjacente em uma janela de consulta do SQL 2012.

Um dos parâmetros solicitados pelo relatório é uma lista de números inteiros. Isso é alcançado no relatório através de uma caixa suspensa de seleção múltipla. A consulta subjacente do relatório usa essa lista inteira na wherecláusula, por exemplo

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

Não quero modificar a consulta que estou depurando, mas não consigo descobrir como criar uma variável no SQL Server que possa conter esse tipo de dados para testá-la.

por exemplo

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

Não há nenhum tipo de dados que possa conter uma lista de números inteiros; portanto, como posso executar a consulta de relatório no meu SQL Server com os mesmos valores que o relatório?


1
Sei que usei o Parâmetro de valor de tabela TVP para inserir dados, mas agora tenho certeza se eles podem ser usados ​​em um local. Sequela?
Paparazzo

2
pergunta bem formulada. 1
RayLoveless

Respostas:


224

Variável de tabela

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

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

ou

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

2
Obrigado por isso, mas novamente é necessário reescrever a maneira como a variável é lida na consulta. Eu tenho que manter o mesmo.
precisa saber é o seguinte

3
E se você não souber quais são os IDs e isso vem de uma consulta? Exemplo: SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234)esta consulta retornará vários IDs e eu recebo um erro dizendo que a subconsulta retornou mais de um resultado e isso não é permitido. Existe alguma maneira de criar uma variável que irá armazenar uma matriz se IDs de uma subconsulta?
Rafael Moreira

1
Eu tentei a segunda opção e funciona para menos número de registros. Quando eu aumento o número de IDs, recebo o erro TimeOut. Talvez o elenco esteja prejudicando o desempenho.
Utilizador H

Não é a resposta para a pergunta original, mas é a resposta para uma que eu não perguntei, então estou bem. Passo em uma lista <int> como parâmetro, mas quero criar uma variável local para testar no SSMS. É matador.
Wade Hatler

Grande resposta, me salvou um monte de tempo
Alfredo A.

35

Assumindo que a variável é algo parecido com:

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

E o procedimento armazenado está usando-o neste formulário:

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

Você pode criar o IntList e chamar o procedimento da seguinte maneira:

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

Ou se você estiver fornecendo o IntList você mesmo

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

17

Você está certo, não há tipo de dados no SQL-Server que possa conter uma lista de números inteiros. Mas o que você pode fazer é armazenar uma lista de números inteiros como uma string.

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

Você pode dividir a sequência em valores inteiros separados e colocá-los em uma tabela. Seu procedimento já pode fazer isso.

Você também pode usar uma consulta dinâmica para obter o mesmo resultado:

DECLARE @SQL nvarchar(8000);

SET @SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + @listOfIDs + ')';
EXECUTE (@SQL);

Obrigado, mas novamente seria necessário modificar uma consulta que eu não tenho permissão.
precisa saber é o seguinte

2
Se alguém usar isso, saiba que isso pode ser muito vulnerável à injeção de SQL se @listOfIDs for um parâmetro de string fornecido por um usuário. Dependendo da arquitetura do seu aplicativo, isso pode ou não ser um problema.
Rogala 25/10

@Rogala Concordado, os usuários precisarão fazer seu próprio saneamento conforme necessário.
Möoz 25/10

1
@ Möoz Eu recomendo adicionar uma nota à sua resposta para refletir isso. Nem todo mundo sabe e simplesmente copia e cola soluções sem pensar nas consequências. O SQL dinâmico é MUITO perigoso, e eu o evito dessa forma.
Rogala

@ Möoz Além disso, não votei na sua resposta, mas, por favor, dedique alguns minutos e confira minha resposta? A função STRING_SPLIT é muito boa, e acho que você estará superando !!
quer

6

Para o SQL Server 2016+ e o Banco de Dados SQL do Azure, foi adicionada a função STRING_SPLIT que seria uma solução perfeita para esse problema. Aqui está a documentação: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Aqui está um exemplo:

/*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;

O resultado da consulta é 1,3

~ Felicidades


4

No final, cheguei à conclusão de que, sem modificar o funcionamento da consulta, não poderia armazenar os valores em variáveis. Usei o SQL Profiler para capturar os valores e depois os codifiquei na consulta para ver como funcionava. Havia 18 dessas matrizes inteiras e algumas tinham mais de 30 elementos.

Eu acho que é necessário que o MS / SQL introduza alguns tipos de dados adicionais na linguagem. Matrizes são bastante comuns e não vejo por que você não pode usá-las em um processo armazenado.


6
O SQL Server não precisa de matrizes, quando possui parâmetros e variáveis ​​com valor de tabela.
John Saunders

1
Então, o que sabemos é que a consulta usa uma lista de números inteiros (transmitidos a ela por uma matriz?). O que eu não entendo é como sua consulta os estava usando sem utilizar um dos métodos fornecidos nas respostas. Forneça um pouco mais de contexto e podemos ajudá-lo ainda mais.
Möoz 27/08/13

2

Você não pode fazer assim, mas pode executar toda a consulta armazenando-a em uma variável.

Por exemplo:

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

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

Exec (@query)

2
Conforme declarado em um comentário anterior, dependendo de como você implementa esse tipo de solução, saiba que isso pode ser vulnerável à injeção de SQL se @listOfIDs for um parâmetro fornecido por um usuário.
Rogala 25/10

2

Há uma nova função no SQL chamada string_splitse você estiver usando a lista de cadeias. Link de referência STRING_SPLIT (Transact-SQL)

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

você pode passar esta consulta da inseguinte maneira:

SELECT *
  FROM [dbo].[yourTable]
  WHERE (strval IN (SELECT value FROM STRING_SPLIT(@tags, ',') WHERE RTRIM(value) <> ''))

1
Parece plausível, mas apenas o SQL Server 2016 em diante, infelizmente.
AnotherFineMess

0

Eu uso isso :

1-Declare uma variável da tabela temporária no script do seu edifício:

DECLARE @ShiftPeriodList TABLE(id INT NOT NULL);

2-Alocar para a tabela temporária:

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-Faça referência à tabela quando você precisar em uma declaração WHERE:

INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM @ShiftPeriodList)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.