【问题标题】:Need SQL Query Help, matching a stored procedure list parameter against individual columns需要 SQL 查询帮助,将存储过程列表参数与各个列匹配
【发布时间】:2009-05-01 20:28:14
【问题描述】:

我有一个存储过程..

Proc:
spMyStoredProcedure

获取参数:

@List varchar(255)

@List 将是一个逗号分隔的值列表,即... @List = '1,2,3'
(清晰......单个值 1 将意味着 col1 = true 的所有记录)

我有一个包含这些列的表:ID int、col1 bit、col2 bit、col3 bit , col4 位。

ID | col1 | col2 | col3 | col4
------------------------------
12 |   0  |   1  |   0  |  0
13 |   1  |   0  |   0  |  0
14 |   0  |   0  |   1  |  0
15 |   0  |   0  |   0  |  1

我希望我的结果仅包含列表中这些行的 ID。 即 12、13、14。

我的第一个想法是遍历列表并进行选择。 IE。对于第一个值为 1 的值,我在 col1 中获取所有具有 1(真)的记录(导致记录 12)。然后移动到第二个值为 2 并在 col2 中获取所有记录为 1(真)(导致记录 14)等等。我想知道是否有更有效/更好/更清洁的方法来做到这一点?

【问题讨论】:

  • 我觉得你的问题不够清楚

标签: sql tsql stored-procedures


【解决方案1】:

我认为这解决了问题:

declare @sql as nvarchar(max)

set @sql = 'select * from table where col' + 
           replace(@list, ',', '=1 or col') + '=1'
sp_executesql @sql

这是假设列表不是用户生成的,而是代码生成的,所以我没有防范 SQL 注入攻击。像这样的列表通常不是用户生成的,所以这就是我假设的原因。

这是你要找的吗?

【讨论】:

  • +1 巧妙地使用 replace()。这比我上面自己的解决方案要好得多。
【解决方案2】:

如果您使用的是 Sql Server 2005,您应该能够使用 PIVOT 函数来转置您的数据。

【讨论】:

    【解决方案3】:

    考虑以下情况,使用动态 SQL。这可能不是最好的方法,但这是我能想到的不对您的表发出多个选择语句的唯一方法。这假设您已经清理了值列表并且您首先完成了错误检查...

    CREATE TABLE #tmp
    (
        ID INT,
        col1 BIT,
        col2 BIT,
        col3 BIT,
        col4 BIT
    )
    INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (12,0,1,0,0)
    INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (13,1,0,0,0)
    INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (14,0,0,1,0)
    INSERT INTO #tmp (ID, col1, col2, col3, col4) VALUES (15,0,0,0,1)
    
    DECLARE @List VARCHAR(255)
    SELECT @List = '1,2,3'
    
    -- create dynamic sql statement
    DECLARE @sql VARCHAR(MAX)
    SET @sql = 'SELECT ID FROM #tmp WHERE '
    
    -- append where statement
    IF CHARINDEX('1',@List) > 0 BEGIN
        SET @sql = @sql + 'col1 = 1 OR '
    END
    IF CHARINDEX('2',@List) > 0 BEGIN
        SET @sql = @sql + 'col2 = 1 OR '
    END
    IF CHARINDEX('3',@List) > 0 BEGIN
        SET @sql = @sql + 'col3 = 1 OR '
    END
    IF CHARINDEX('4',@List) > 0 BEGIN
        SET @sql = @sql + 'col4 = 1 OR '
    END
    
    -- remove the trailing 'OR' and execute
    SET @sql = SUBSTRING(@sql,0,LEN(@sql)-2)
    EXEC (@sql)
    

    【讨论】:

      【解决方案4】:

      通常我使用计算列来存储位列的总数。假设您无法定义计算列,我们可以使用类似的方法

      SELECT ID FROM SampleTable WHERE (col1*1 + col2*2 + col3*4 + col4*8) IN (1, 2, 3)
      

      由于 1,2,3 都连接在一个变量中,因此我通常使用 http://www.sqlteam.com/article/using-a-csv-with-an-in-sub-select 中定义的用户定义函数。

      所以,应该是这样的

      SELECT ID FROM SampleTable WHERE (col1*1 + col2*2 + col3*4 + col4*8) IN dbo.CSVToInt(@List)
      

      【讨论】:

        【解决方案5】:

        ** 错误答案,请查看 Eric 的答案以获得更好的答复!

        幸运的是,您的列表似乎与 IN 格式匹配:

         select * from yourtable where id in (1,2,3,4)
        

        因此您可以轻松构建动态查询:

        declare @query varchar(4000)
        set @query = 'select * from yourtable where id in (' +
            @list + ')'
         exec sp_executesql @query
        

        请记住,您的客户端必须确保 @list 参数是注入干净的。您可以在存储过程中验证它不包含任何单引号。

        【讨论】:

        • -1 再次(对不起,其他人发布了一个问题并删除了它)..这使您可以进行 sql 注入..
        • 我不同意 Myagi。如果您在数据最终进入存储过程之前没有清理数据,那么您做错了。不应使用 T-SQL 来防止注入。
        • 许多应用程序使用动态 SQL 而不容易受到注入攻击(我确信 stackoverflow 就是其中之一。)
        • @Andomar - 关于使用动态 sql,我不同意你的看法……但这种特殊情况为 Sql Injection 敞开了大门……
        • 不,他们不会在动态 sql 的情况下。我建议你阅读一下。即使在参数化的情况下,当使用参数直接输入 dynamic sql 时,也会导致执行任意代码。无论如何,在这种情况下,我认为修改 sql 注入是愚蠢的,只要您相信列表的来源,就不会发生这种情况。
        【解决方案6】:

        我们使用解析逗号列表并返回表值的 UDF:

        CREATE FUNCTION [dbo].[ListToTable] (
          @list VARCHAR(MAX),
          @separator VARCHAR(MAX) = ','
        )
        RETURNS @table TABLE (Value VARCHAR(MAX))
        AS BEGIN
           DECLARE @position INT, @previous INT
           SET @list = @list + @separator
           SET @previous = 1
           SET @position = CHARINDEX(@separator, @list)
           WHILE @position > 0 BEGIN
              IF @position - @previous > 0
                 INSERT INTO @table VALUES (LTRIM(RTRIM(SUBSTRING(@list, @previous, @position - @previous))))
              IF @position >= LEN(@list) BREAK
              SET @previous = @position + 1
              SET @position = CHARINDEX(@separator, @list, @previous)
           END
           RETURN
        END
        

        然后你可以这样做:

        SELECT *
        FROM MyTable m
        JOIN dbo.ListToTable(@List) l
          ON m.ID = l.Value
        

        上面的函数也有一个可选的分隔符,如下所示:

        SELECT *
        FROM MyTable m
        JOIN dbo.ListToTable('1:2:3', ':') l
          ON m.ID = l.Value
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-09-19
          • 1970-01-01
          • 2011-04-03
          • 2010-12-26
          • 1970-01-01
          • 2020-01-10
          • 1970-01-01
          相关资源
          最近更新 更多