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

发布于 2024-07-30 18:36:35 字数 514 浏览 6 评论 0原文

我刚刚遇到了一件奇怪的事情...我们网站上的一些代码采用了一个巨大的 SQL 语句,通过基于某些用户值进行一些搜索和替换来修改代码,然后将其传递到 SQL Server 作为一个问题。

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

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

是否有一种简单明了的方法可以做到这一点? 我所做的一切似乎都不优雅。

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

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

I just ran into a strange thing...there is some code on our site that is taking a giant SQL statement, modifying it in code by doing some search and replace based on some user values, and then passing it on to SQL Server as a query.

I was thinking that this would be cleaner as a parameterized query to a stored proc, with the user values as the parameters, but when I looked more closely I see why they might be doing it...the table that they are selecting from is variably dependant on those user values.

For instance, in one case if the values were ("FOO", "BAR") the query would end up being something like "SELECT * FROM FOO_BAR"

Is there an easy and clear way to do this? Everything I'm trying seems inelegant.

EDIT: I could, of course, dynamically generate the sql in the stored proc, and exec that (bleh), but at that point I'm wondering if I've gained anything.

EDIT2: Refactoring the table names in some intelligent way, say having them all in one table with the different names as a new column would be a nice way to solve all of this, which several people have pointed out directly, or alluded to. Sadly, it is not an option in this case.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

凤舞天涯 2024-08-06 18:36:36

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

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

I would argue against dynamically generating the SQL in the stored proc; that'll get you into trouble and could cause injection vulnerability.

Instead, I would analyze all of the tables that could be affected by the query and create some sort of enumeration that would determine which table to use for the query.

尴尬癌患者 2024-08-06 18:36:36

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

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

Sounds like you'd be better off with an ORM solution.

I cringe when I see dynamic sql in a stored procedure.

冷…雨湿花 2024-08-06 18:36:36

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

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

One thing you can consider is to make a case statement that contains the same SQL command you want, once for each valid table, then pass as a string the table name into this procedure and have the case choose which command to run.

By the way as a security person the suggestion above telling you to select from the system tables in order to make sure you have a valid table seems like a wasted operation to me. If someone can inject passed the QUOTENAME() then then injection would work on the system table just as well as on the underlying table. The only thing this helps with it to ensure it is a valid table name, and I think the suggestion above is a better approach to that since you are not using QUOTENAME() at all.

奢望 2024-08-06 18:36:36

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

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

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

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

Depending on whether the set of columns in those tables is the same or different, I'd approach it in two ways in the longer term:

1) if they the same, why not create a new column that would be used as a selector, whose value is derived from the user-supplied parameters ? (is it a performance optimization?)

2) if they are different, chances are that handling of them is also different. As such, it seems like splitting the select/handle code into separate blocks and then calling them separately would be a most modular approach to me. You will repeat the "select * from" part,
but in this scenario the set of tables is hopefully finite.

Allowing the calling code to supply two arbitrary parts of the table name to do a select from feels very dangerous.

路还长,别太狂 2024-08-06 18:36:36

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

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

I don't know the reason why you have the data spread over several tables, but it sounds like you are breaking one of the fundamentals. The data should be in the tables, not as table names.

If the tables have more or less the same layout, consider if it would be best to put the data in a single table instead. That would solve your problem with the dynamic query, and it would make the database layout more flexible.

浅紫色的梦幻 2024-08-06 18:36:36

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

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

Instead of Querying the tables based on user input values, you can pick the procedure instead.
that is to say
1. Create a procedure FOO_BAR_prc and inside that you put the query 'select * from foo_bar' , that way the query will be precompiled by the database.
2. Then based on the user input now execute the correct procedure from your application code.

Since you have around 50 tables, this might not be a feasible solution though as it would require lot of work on your part.

请止步禁区 2024-08-06 18:36:36

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

使用[数据库名称]

/***** 对象:StoredProcedure [dbo].[sp_CreateDynamicTable] 脚本日期:06/20/2015 16:56:25 ******/
设置 ANSI_NULLS 为开

将 QUOTED_IDENTIFIER 设置为 ON

创建过程 [dbo].[sp_CreateDynamicTable]
@tName varchar(255)
作为
开始
设置不计数;
声明 @SQL nvarchar(max)

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

    EXECUTE sp_executesql @SQL

END

In fact, I wanted to know how to pass table name to create a table in stored procedure. By reading some of the answers and attempting some modification at my end, I finally able to create a table with name passed as parameter. Here is the stored procedure for others to check any error in it.

USE [Database Name]
GO
/****** Object: StoredProcedure [dbo].[sp_CreateDynamicTable] Script Date: 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;
DECLARE @SQL nvarchar(max)

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

    EXECUTE sp_executesql @SQL

END

木緿 2024-08-06 18:36:36

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

创建过程 spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS
-- 计算任何非系统表的行数,安全
开始
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

@RBarry Young
You don't need to add the brackets to @ActualTableName in the query string because it is already included in the result from the query in the INFORMATION_SCHEMA.TABLES. Otherwise, there will be error(s) when executed.

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 + '];'

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

EXEC(@SQL)

END

一笔一画续写前缘 2024-08-06 18:36:36

我会不惜一切代价避免动态 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;

I would avoid dynamic SQL at all costs.

Isn't the most elegant solution but does the job perfectly.

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;
往日 2024-08-06 18:36:36

您可以在存储过程中使用准备好的语句来执行查询。

简单示例演示:

DELIMITER //
CREATE PROCEDURE example (IN p_table_name VARCHAR(20), IN p_col VARCHAR(20))
BEGIN
  SET @sql = CONCAT('SELECT ', p_col, '_input, ', p_col, '_output', ' FROM ', p_table_name);
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
  DEALLOCATE PREPARE stmt;
END//
DELIMITER ;

这里,如果表 input_output 中有“first_input”、“first_output”、“second_input”和“second_output”等列。

如果要从表 input_output 中检索“first_input”、“first_output”的数据:

call example('input_output', 'first');

You can use the prepared statement within your stored procedure for executing query.

Simple example demo:

DELIMITER //
CREATE PROCEDURE example (IN p_table_name VARCHAR(20), IN p_col VARCHAR(20))
BEGIN
  SET @sql = CONCAT('SELECT ', p_col, '_input, ', p_col, '_output', ' FROM ', p_table_name);
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
  DEALLOCATE PREPARE stmt;
END//
DELIMITER ;

Here, if you have columns like "first_input", "first_output", "second_input", and "second_output" from table input_output.

If you want to retrieve data of "first_input", "first_output" from table input_output:

call example('input_output', 'first');
Bonjour°[大白 2024-08-06 18:36:35

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


更多问题解答:

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

  2. QUOTENAME 对于本示例来说并不是绝对必要的,仅 INFORMATION_SCHEMA 上的查找翻译通常就足够了。 QUOTENAME 放在这里是因为从安全角度考虑,包含完整且正确的解决方案是一种良好的形式。 这里的 QUOTENAME 实际上是在防止一个独特但类似的潜在问题,即潜在注入


我应该指出,您可以使用动态列名称和 INFORMATION_SCHEMA.COLUMNS 表执行相同的操作。

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

First of all, you should NEVER do SQL command compositions on a client app like this, that's what SQL Injection is. (Its OK for an admin tool that has no privs of its own, but not for a shared use application).

Secondly, yes, a parametrized call to a Stored procedure is both cleaner and safer.

However, as you will need to use Dynamic SQL to do this, you still do not want to include the passed string in the text of the executed query. Instead, you want to used the passed string to look up the names of the actual tables that the user should be allowed to query in the way.

Here's a simple naive example:

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

Some have fairly asked why this is safer. Hopefully, little Bobby Tables can make this clearer:
0
alt text


Answers to more questions:

  1. QUOTENAME alone is not guaranteed to be safe. MS encourages us to use it, but they have not given a guarantee that it cannot be out-foxed by hackers. FYI, real Security is all about the guarantees. The table lookup with QUOTENAME, is another story, it's unbreakable.

  2. QUOTENAME is not strictly necessary for this example, the Lookup translation on INFORMATION_SCHEMA alone is normally sufficient. QUOTENAME is in here because it is good form in security to include a complete and correct solution. QUOTENAME in here is actually protecting against a distinct, but similar potential problem know as latent injection.


I should note that you can do the same thing with dynamic Column Names and the INFORMATION_SCHEMA.COLUMNS table.

You can also bypass the need for stored procedures by using a parameterized SQL query instead (see here: https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=netframework-4.8). But I think that stored procedures provide a more manageable and less error-prone security facility for cases like this.

怪我太投入 2024-08-06 18:36:35

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

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

(Un)fortunately there's no way of doing this - you can't use table name passed as a parameter to stored code other than for dynamic sql generation. When it comes to deciding where to generate sql code, I prefer application code rather that stored code. Application code is usually faster and easier to maintain.

In case you don't like the solution you're working with, I'd suggest a deeper redesign (i.e. change the schema/application logic so you no longer have to pass table name as a parameter anywhere).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文