Переменная SQL для хранения списка целых чисел

175

Я пытаюсь отладить чужие отчеты SQL и поместил базовый запрос отчетов в окна запросов SQL 2012.

Одним из параметров, запрашиваемых в отчете, является список целых чисел. Это достигается в отчете с помощью раскрывающегося списка с несколькими вариантами выбора. Основной запрос отчета использует этот список целых чисел в whereпредложении, например:

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

Я не хочу изменять отлаживаемый запрос, но не могу понять, как создать на SQL Server переменную, которая может содержать данные этого типа для его проверки.

например

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

Не существует типа данных, который может содержать список целых чисел, так как я могу выполнить запрос отчета на моем SQL Server с теми же значениями, что и отчет?

ErickTreetops
источник
1
Я знаю, что использовал параметр TVP Table Value Parmeter для вставки данных, но теперь уверен, что его можно использовать где угодно. Сиквел?
Папараццо
2
хорошо сформулированный вопрос. +1
RayLoveless

Ответы:

224

Переменная таблицы

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

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

или

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
источник
2
Спасибо за это, но опять же мне нужно переписать способ чтения переменной в запросе. Я должен держать это то же самое.
ErickTreetops
3
Что делать, если вы не знаете, что это за идентификаторы и что происходит из запроса? Пример: SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234)этот запрос вернет несколько идентификаторов, и я получаю сообщение об ошибке, в котором говорится, что подзапрос возвратил более одного результата, и это недопустимо. Есть ли в любом случае создать переменную, которая будет хранить массив, если идентификаторы из подзапроса?
Рафаэль Морейра
1
Я попробовал второй вариант, и он работает для меньшего количества записей. Когда я увеличиваю количество идентификаторов, я получаю ошибку TimeOut. Возможно, актерский состав мешает исполнению.
Пользователь M
Не ответ на оригинальный вопрос, но это ответ на вопрос, который я не задавал, так что я в порядке. Я передаю List <int> в качестве параметра, но хочу создать локальную переменную для тестирования в SSMS. Это убийца
Уэйд Хэтлер
Отличный ответ, сэкономил мне много времени
Альфредо А.
35

Предполагая, что переменная похожа на:

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

И хранимая процедура использует его в этой форме:

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

Вы можете создать IntList и вызвать процедуру следующим образом:

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

Или, если вы предоставляете IntList самостоятельно

DECLARE @listOfIDs dbo.IntList
INSERT INTO @listofIDs VALUES (1),(35),(118);
Уильям Мюллер
источник
17

Вы правы, в SQL-сервере нет типа данных, который может содержать список целых чисел. Но вы можете сохранить список целых чисел в виде строки.

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

Затем вы можете разбить строку на отдельные целочисленные значения и поместить их в таблицу. Ваша процедура может уже сделать это.

Вы также можете использовать динамический запрос для достижения того же результата:

DECLARE @SQL nvarchar(8000);

SET @SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + @listOfIDs + ')';
EXECUTE (@SQL);
MOOZ
источник
Спасибо, но опять же нужно будет изменить запрос, который мне не разрешен.
ErickTreetops
2
Если кто-то использует это, имейте в виду, что это может быть очень уязвимо для внедрения SQL, если @listOfIDs является строковым параметром, предоставленным пользователем. В зависимости от вашей архитектуры вашего приложения, это может или не может быть проблемой.
Rogala
@Rogala Согласен, пользователи должны будут выполнять свою собственную санитарную обработку по мере необходимости.
Möoz
1
@ Möoz Я рекомендую добавить примечание к вашему ответу, чтобы отразить это. Не все знают, и они просто копируют и вставляют решения, не задумываясь о последствиях. Динамический SQL ОЧЕНЬ опасен, и я избегаю его как эта чума.
Rogala
@ Möoz Кроме того, я не голосовал за твой ответ, но что ты можешь уделить несколько минут и проверить мой ответ? Функция STRING_SPLIT довольно мила, и я думаю, что вы будете ВСЕМ над ней !!
Rogala
6

Для SQL Server 2016+ и базы данных SQL Azure была добавлена ​​функция STRING_SPLIT, которая была бы идеальным решением для этой проблемы. Вот документация: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Вот пример:

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

Результат запроса 1,3

~ Приветствия

Rogala
источник
4

В итоге я пришел к выводу, что без изменения работы запроса я не смогу хранить значения в переменных. Я использовал SQL profiler, чтобы перехватить значения, а затем жестко запрограммировал их в запрос, чтобы увидеть, как это работает. Было 18 из этих целочисленных массивов, а в некоторых содержалось более 30 элементов.

Я думаю, что MS / SQL необходимо ввести некоторые дополнительные типы данных в язык. Массивы довольно распространены, и я не понимаю, почему вы не можете использовать их в хранимых процессах.

ErickTreetops
источник
6
SQL Server не требует массивов, если он имеет табличные параметры и переменные.
Джон Сондерс
1
Итак, мы знаем, что запрос использует список целых чисел (переданный ему массивом?). Что я не понимаю, так это то, как ваш запрос использовал их, не используя один из указанных методов в ответах. Предоставьте больше контекста, и мы можем помочь вам в дальнейшем.
Möoz
2

Вы не можете сделать это так, но вы можете выполнить весь запрос, сохраняя его в переменной.

Например:

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

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

Exec (@query)
thepirat000
источник
2
Как указано в предыдущем комментарии, в зависимости от того, как вы реализуете этот тип решения, имейте в виду, что это может быть уязвимо для внедрения SQL, если @listOfIDs является параметром, предоставленным пользователем.
Rogala
2

В SQL есть новая функция, которая вызывается, string_splitесли вы используете список строк. Ссылка на ссылку STRING_SPLIT (Transact-SQL)

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

Вы можете передать этот запрос inследующим образом:

SELECT *
  FROM [dbo].[yourTable]
  WHERE (strval IN (SELECT value FROM STRING_SPLIT(@tags, ',') WHERE RTRIM(value) <> ''))
Рави Ананд
источник
1
Выглядит правдоподобно, но, к сожалению, только SQL Server 2016 года.
AnotherFineMess
0

Я использую это:

1-Объявите переменную временной таблицы в скрипте вашего здания:

DECLARE @ShiftPeriodList TABLE(id INT NOT NULL);

2-Распределить во временную таблицу:

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-Ссылка на таблицу, когда вам это нужно в выражении WHERE:

INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM @ShiftPeriodList)
Макс Александр Ханна
источник