繁体   English   中英

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

[英]How should I pass a table name into a stored proc?

我刚刚遇到了一件奇怪的事情......我们网站上有一些代码采用了巨大的 SQL 语句,通过根据某些用户值进行一些搜索和替换来在代码中修改它,然后将它传递给 SQL 服务器作为一个问题。

我当时认为这将作为对存储过程的参数化查询更清晰,将用户值作为参数,但是当我更仔细地观察时,我明白了他们为什么会这样做......他们从中选择的表是取决于这些用户值。

例如,在一种情况下,如果值是 ("FOO", "BAR"),则查询最终会变成类似于 "SELECT * FROM FOO_BAR"

有没有简单明了的方法来做到这一点? 我正在尝试的一切似乎都不优雅。

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

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

首先,您永远不应该像这样在客户端应用程序上执行 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

有些人已经相当地问为什么这更安全。 希望小鲍比表可以更清楚地说明这一点:0替代文字


更多问题的答案:

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

  2. QUOTENAME 在这个例子中不是绝对必要的,仅在 INFORMATION_SCHEMA 上的 Lookup 翻译通常就足够了。 QUOTENAME 在这里是因为包含完整和正确的解决方案是安全的好形式。 这里的 QUOTENAME 实际上是在防止一个不同但类似的潜在问题,称为潜在注入


我应该注意到,您可以对动态列名和INFORMATION_SCHEMA.COLUMNS表做同样的事情。

您还可以通过使用参数化 SQL 查询来绕过对存储过程的需求(请参阅此处: https : //docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=网络框架-4.8 )。 但我认为存储过程为此类情况提供了更易于管理且不易出错的安全设施。

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

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

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

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

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

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

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

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

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

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

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

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

我不知道您将数据分布在多个表中的原因,但听起来您正在打破其中一个基本原理。 数据应该在表中,而不是作为表名。

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

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

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

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

USE [数据库名称] GO /****** 对象:StoredProcedure [dbo].[sp_CreateDynamicTable] 脚本日期:06/20/2015 16:56:25 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [dbo].[sp_CreateDynamicTable] @tName varchar(255) AS BEGIN SET NOCOUNT ON; 声明 @SQL nvarchar(max)

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

    EXECUTE sp_executesql @SQL

结尾

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

CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS -- 计算任何非系统表中的行数,安全开始声明 @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)

结尾

我会不惜一切代价避免动态 SQL。

这不是最优雅的解决方案,但可以完美地完成工作。

PROCEDURE TABLE_AS_PARAMTER (
        p_table_name IN VARCHAR2
    ) AS
    BEGIN
        CASE p_table_name
            WHEN 'TABLE1' THEN
                UPDATE TABLE1
                SET
                    COLUMN1 =1
                WHERE
                    ID =1;
            WHEN 'TABLE2' THEN
                UPDATE TABLE1
                SET
                    COLUMN1 =1
                WHERE
                    ID =2;
        END CASE;

        COMMIT;
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK
END TABLE_AS_PARAMTER;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM