我应该如何将表名传递给存储过程?

时间:2009-08-07 20:09:38

标签: sql sql-server parameterized

我只是遇到了一个奇怪的事情......我们网站上有一些代码正在采用一个巨大的SQL语句,通过执行一些搜索来修改代码并根据某些用户值进行替换,然后将其传递给SQL Server作为查询。

我认为这对于存储过程的参数化查询会更清晰,用户的值作为参数,但是当我仔细观察时,我明白为什么他们可能会这样做...他们是的表是选择是根据用户的价值而变化的。

例如,在一种情况下,如果值为(“FOO”,“BAR”),则查询最终会变为“SELECT * FROM FOO_BAR”

有一种简单明了的方法吗?我正在尝试的一切似乎都不够优雅。

编辑:当然,我可以在存储过程中动态生成sql,并执行(bleh),但那时我想知道我是否获得了任何东西。< / p>

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

10 个答案:

答案 0 :(得分:39)

首先,您应该从不在这样的客户端应用上执行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 Tables可以让这个更清晰:

alt text


更多问题的答案:

  1. 单独的QUOTENAME不保证是安全的。 MS鼓励我们使用它,但是它们没有保证它不会被黑客搞砸。仅供参考,真正的安全就是保证。使用QUOTENAME查找表格是另一个故事,它是牢不可破的。

  2. QUOTENAME对于此示例并非严格必要,仅在INFORMATION_SCHEMA上的Lookup转换通常就足够了。 QUOTENAME就在这里,因为它包含一个完整而正确的解决方案,是安全的好形式。这里的QUOTENAME实际上是针对一个明显但类似的潜在问题,即潜伏注射

答案 1 :(得分:5)

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

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

答案 2 :(得分:2)

我反对在存储过程中动态生成SQL;这会让你陷入麻烦,并可能导致注射漏洞。

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

答案 3 :(得分:2)

听起来你的ORM解决方案会更好。

当我在存储过程中看到动态sql时,我感到畏缩。

答案 4 :(得分:1)

您可以考虑的一件事是创建一个包含所需SQL命令的case语句,每个有效表一次,然后将表名作为字符串传递给此过程,并让case选择要运行的命令。

作为一名安全人员,上面的建议告诉您从系统表中进行选择以确保您拥有一个有效的表格,这对我来说似乎是一种浪费。如果有人可以注入传递QUOTENAME()然后注入将在系统表上以及基础表上工作。唯一有用的是确保它是一个有效的表名,我认为上面的建议是更好的方法,因为你根本没有使用QUOTENAME()。

答案 5 :(得分:0)

根据这些表中的列集是相同还是不同,我会在较长期内以两种方式处理它:

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

2)如果它们不同,那么处理它们的可能性也会不同。因此,似乎将选择/句柄代码拆分为单独的块,然后单独调用它们对我来说是一种最模块化的方法。您将重复“select * from”部分, 但在这种情况下,表格集有望是有限的。

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

答案 6 :(得分:0)

我不知道您将数据分布在多个表中的原因,但听起来您正在破坏其中一个基础。数据应该在表格中,而不是表格名称。

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

答案 7 :(得分:0)

您可以选择该过程,而不是根据用户输入值查询表。 也就是说     1.创建一个程序FOO_BAR_prc并在其中放入查询'select * from foo_bar',这样查询将由数据库预编译。
    2.然后根据用户输入,现在从应用程序代码执行正确的过程。

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

答案 8 :(得分:0)

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

USE [数据库名称] 走 / ******对象:StoredProcedure [dbo]。[sp_CreateDynamicTable]脚本日期:06/20/2015 16:56:25 ****** / SET ANSI_NULLS ON 走 SET QUOTED_IDENTIFIER ON 走 CREATE PROCEDURE [dbo]。[sp_CreateDynamicTable]     @tName varchar(255) 如 开始     设置NOCOUNT ON;     DECLARE @SQL nvarchar(max)

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

    EXECUTE sp_executesql @SQL

END

答案 9 :(得分:0)

@RBarry Young 您不需要在查询字符串中将括号添加到@ActualTableName,因为它已包含在INFORMATION_SCHEMA.TABLES中查询的结果中。否则,执行时会出现错误。

CREATE PROC spCountAnyTableRows(@PassedTableName as NVarchar(255))AS - 计算来自任何非系统表的行数, SAFELY 开始     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 + '];'

-- changed to this
SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

EXEC(@SQL)

END