【问题标题】:SQL variable to hold list of integers保存整数列表的 SQL 变量
【发布时间】:2013-08-24 16:24:05
【问题描述】:

我正在尝试调试其他人的 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 上使用与报表相同的值运行报表查询?

【问题讨论】:

  • 我知道我已经使用 TVP 表值参数来插入数据,但现在确定它是否可以在 where 中使用。续集?
  • 措辞得当的问题。 +1

标签: sql-server list tsql variables reporting-services


【解决方案1】:

Table variable

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

【讨论】:

  • 谢谢你,但它再次要求我重写在查询中读取变量的方式。我必须保持不变。
  • 如果您不知道 ID 是什么并且来自查询,该怎么办?示例:SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234) 此查询将返回多个 ID,我收到一条错误消息,指出子查询返回了多个结果,这是不允许的。如果来自子查询的 ID,是否有创建一个变量来存储数组?
  • 我尝试了第二个选项,它适用于更少的记录。当我增加 Id 的数量时,出现 TimeOut 错误。也许演员阵容阻碍了表演。
  • 不是原始问题的答案,而是我没有问的问题的答案,所以我很好。我传入一个 List 作为参数,但想创建一个局部变量以在 SSMS 中进行测试。这是杀手锏。
  • 很好的答案,为我节省了很多时间
【解决方案2】:

假设变量类似于:

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);

【讨论】:

    【解决方案3】:

    你是对的,SQL-Server 中没有可以保存整数列表的数据类型。但是您可以做的是将整数列表存储为字符串。

    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);
    

    【讨论】:

    • 谢谢,但再次需要修改我不允许的查询。
    • 如果有人使用它,请注意如果@listOfIDs 是用户提供的字符串参数,这可能很容易受到SQL 注入的影响。根据您应用的架构,这可能是也可能不是问题。
    • @Rogala 同意,用户需要根据需要自己做卫生。
    • @Möoz 我建议在您的答案中添加注释以反映这一点。不是每个人都知道,他们只是简单地复制和粘贴解决方案而不考虑后果。动态 SQL 非常危险,我像瘟疫一样避免它。
    • @Möoz 另外,我没有否决您的答案,但是请您花几分钟时间看看我的答案? STRING_SPLIT 函数非常可爱,我想你会喜欢它的!!
    【解决方案4】:

    对于 SQL Server 2016+ 和 Azure SQL 数据库,添加了 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

    【讨论】:

      【解决方案5】:

      最后我得出的结论是,如果不修改查询的工作方式,我就无法将值存储在变量中。我使用 SQL 分析器来捕获这些值,然后将它们硬编码到查询中以查看它是如何工作的。这些整数数组有 18 个,其中一些包含 30 多个元素。

      我认为 MS/SQL 需要在语言中引入一些额外的数据类型。数组很常见,我不明白为什么不能在存储过程中使用它们。

      【讨论】:

      • SQL Server 在具有表值参数和变量时不需要数组。
      • 所以我们知道查询使用了一个整数列表(通过数组传递给它?)。我不明白的是您的查询是如何使用它们而不使用答案中给定的方法之一。提供更多背景信息,我们可以进一步帮助您。
      【解决方案6】:

      如果您使用的是字符串列表,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) <> ''))
      

      【讨论】:

      • 看起来有道理,但不幸的是只有 SQL Server 2016 以后。
      【解决方案7】:

      我用这个:

      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)
      

      【讨论】:

        【解决方案8】:

        你不能这样做,但你可以执行整个查询,将它存储在一个变量中。

        例如:

        DECLARE @listOfIDs NVARCHAR(MAX) = 
            '1,2,3'
        
        DECLARE @query NVARCHAR(MAX) = 
            'Select *
             From TabA
             Where TabA.ID in (' + @listOfIDs + ')'
        
        Exec (@query)
        

        【讨论】:

        • 如前一条评论中所述,根据您实施此类解决方案的方式,请注意,如果 @listOfIDs 是用户提供的参数,则这可能容易受到 SQL 注入的攻击。
        猜你喜欢
        • 1970-01-01
        • 2020-04-13
        • 2013-11-10
        • 2013-07-19
        • 2018-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多