代码之家  ›  专栏  ›  技术社区  ›  Beska

如何将表名传递到存储过程中?

  •  13
  • Beska  · 技术社区  · 15 年前

    我刚刚遇到一件奇怪的事情……我们的站点上有一些代码采用了一个巨大的SQL语句,通过基于一些用户值进行搜索和替换,在代码中对其进行修改,然后将其作为查询传递给SQL Server。

    我在想,作为对存储过程的参数化查询,使用用户值作为参数会更干净,但是当我更仔细地观察时,我明白了他们为什么要这样做……他们选择的表取决于这些用户值。

    例如,在一种情况下,如果值为(“foo”,“bar”),则查询结果将类似于“select*from foo_bar”

    有没有一种简单明了的方法来做到这一点?我所尝试的一切似乎都很糟糕。

    编辑: 当然,我可以在存储过程中动态地生成SQL,并执行它(bleh),但在这一点上,我想知道我是否获得了什么。

    编辑2: 以某种智能的方式重构表名,比如将它们全部放在一个具有不同名称的表中作为一个新列,这将是解决所有这些问题的一个好方法,这是一些人直接指出或暗示的。不幸的是,在这种情况下,这不是一个选择。

    10 回复  |  直到 8 年前
        1
  •  37
  •   Community Aniket Inge    7 年前

    首先,你应该 从未 在这样的客户端应用程序上执行SQL命令组合, 那是 什么是SQL注入。(对于没有自己特权的管理工具来说是可以的,但对于共享使用应用程序则不是这样)。

    其次,是的,对存储过程的参数化调用既干净又安全。

    然而 ,因为您需要使用动态SQL来执行此操作,所以仍然不希望在执行的查询的文本中包含传递的字符串。相反,您希望使用传递的字符串查找 实际的 允许用户以这种方式查询的表。

    下面是一个简单天真的例子:

    CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS
    -- Counts the number of rows from any non-system Table, *SAFELY*
    BEGIN
        DECLARE @ActualTableName AS NVarchar(255)
    
        SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
        FROM INFORMATION_SCHEMA.TABLES
        WHERE TABLE_NAME = @PassedTableName
    
        DECLARE @sql AS NVARCHAR(MAX)
        SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'
    
        EXEC(@SQL)
    END
    

    有些人已经公平地问了为什么这样更安全。希望Bobby的小桌子能更清楚地说明这一点:

    alt text


    更多问题的答案:

    1. 单是QuoteName并不能保证安全。微软鼓励我们使用它,但他们并没有保证它不会被黑客所欺骗。仅供参考,真正的安全就是保证。QuoteName的表查找是另一个故事,它是牢不可破的。

    2. 对于这个例子来说,QuoteName并不是绝对必要的,仅在信息模式上进行查找翻译通常就足够了。QuoteName之所以出现在这里,是因为它是一种很好的安全形式,可以包含完整和正确的解决方案。这里的QuoteName实际上是针对一个明显但类似的潜在问题进行保护,即 潜伏期注射 .

        2
  •  5
  •   AlexS    15 年前

    (un)幸运的是,没有办法做到这一点——除了用于动态SQL生成之外,不能将表名作为参数传递给存储代码。在决定在哪里生成SQL代码时,我更喜欢应用程序代码,而不是存储的代码。应用程序代码通常更快、更容易维护。

    如果您不喜欢正在使用的解决方案,我建议您进行更深入的重新设计(即更改架构/应用程序逻辑,这样您就不必在任何地方将表名作为参数传递)。

        3
  •  2
  •   Randolpho    15 年前

    我反对在存储过程中动态生成SQL;这会给您带来麻烦,并可能导致注入漏洞。

    相反,我将分析可能受查询影响的所有表,并创建某种枚举,以确定用于查询的表。

        4
  •  2
  •   ScottE    15 年前

    听起来你最好使用ORM解决方案。

    当我在存储过程中看到动态SQL时,我会感到害怕。

        5
  •  1
  •   pilavdzice    13 年前

    您可以考虑的一件事是生成一个包含您想要的相同SQL命令的case语句,对每个有效表执行一次,然后将表名作为字符串传递到此过程中,并让case选择要运行的命令。

    顺便说一下,作为一个安全人员,上面的建议告诉您从系统表中进行选择,以确保您有一个有效的表,这对我来说似乎是一个浪费的操作。如果有人可以注入传递了QuoteName(),那么注入将在系统表和底层表上工作。唯一有助于确保它是一个有效的表名的方法,我认为上面的建议是更好的方法,因为您根本不使用QuoteName()。

        6
  •  0
  •   Andrew Y    15 年前

    根据这些表中的列集是相同的还是不同的,从长远来看,我将以两种方式处理它:

    1)如果它们相同,为什么不创建一个将用作选择器的新列,该列的值是从用户提供的参数派生的?(这是性能优化吗?)

    2)如果它们不同,处理它们的机会也不同。因此,似乎将select/handle代码拆分为单独的块,然后分别调用它们是我最模块化的方法。您将重复“select*from”部分, 但在这种情况下,表集可能是有限的。

    允许调用代码提供表名的两个任意部分来进行选择,感觉非常危险。

        7
  •  0
  •   Guffa    15 年前

    我不知道为什么数据分散在几个表上,但听起来你破坏了其中一个基本原理。数据应该在表中,而不是作为表名。

    如果表的布局或多或少相同,请考虑是否最好将数据放在单个表中。这将解决动态查询的问题,并使数据库布局更加灵活。

        8
  •  0
  •   Rajat    15 年前

    您可以选择该过程,而不是根据用户输入值查询表。 这就是说
    1。创建一个过程foo-bar-prc,然后在其中放置查询“select*from foo-bar”,这样数据库将预编译查询。
    2。然后根据用户输入,从应用程序代码中执行正确的过程。

    因为您有大约50个表,所以这可能不是一个可行的解决方案,因为这需要您做很多工作。

        9
  •  0
  •   Sushil Pugalia    9 年前

    实际上,我想知道如何传递表名以在存储过程中创建表。通过阅读一些答案并尝试在末尾进行一些修改,我最终能够创建一个以名称作为参数传递的表。下面是供其他人检查其中任何错误的存储过程。

    使用[数据库名称] 去 /******对象:storedprocedure[dbo]。[sp eu createddynamicable]脚本日期:2015年6月20日16:56:25******/ 将Ansi_Nulls设置为打开 去 打开带引号的标识符 去 创建过程[dbo]。[sp_createddynamicable] @名称varchar(255) AS 开始 设置NoCon; 声明@sql nvarchar(max)

    SET @SQL = N'CREATE TABLE [DBO].['+ @tName + '] (DocID nvarchar(10) null);'
    
        EXECUTE sp_executesql @SQL
    

    结束

        10
  •  0
  •   omostan    8 年前

    @ RBarry Young 您不需要将方括号添加到查询字符串中的@actualtablename,因为它已经包含在信息“schema.tables”中查询的结果中。否则,执行时将出现错误。

    将proc spcountanytablerows(@passedtablename as nvarchar(255))创建为 --统计任何非系统表中的行数, 安全地 开始 将@actualtablename声明为nvarchar(255)

    SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
    FROM INFORMATION_SCHEMA.TABLES
    WHERE TABLE_NAME = @PassedTableName
    
    DECLARE @sql AS NVARCHAR(MAX)
    --SELECT @sql = 'SELECT COUNT(*) FROM [' + @ActualTableName + '];'
    
    -- changed to this
    SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'
    
    EXEC(@SQL)
    

    结束