SQL Server:如何将数据库名称作为存储过程中的参数

jjo*_*ras 15 sql sql-server stored-procedures sql-server-2008

我正在尝试创建一个查询sys.tables表的简单存储过程.

CREATE PROCEDURE dbo.test
    @dbname NVARCHAR(255),
    @col NVARCHAR(255)
AS
    SET NOCOUNT ON
    SET XACT_ABORT ON

    USE @dbname

    SELECT TOP 100 *
    FROM sys.tables 
    WHERE name = @col
GO
Run Code Online (Sandbox Code Playgroud)

这似乎不起作用,因为我应该在USE @dbname之后放置GO,但这会终止此过程的创建吗?如何将此数据库选择放入此过程中,以便用户可以将数据库名称作为此proc的参数?

Mar*_*ith 26

如果使用EXEC @Var(不带括号 - 即不使用 EXEC (@Var)),SQL Server将查找与传入的名称匹配的存储过程@Var.您可以使用三部分命名.

如果sys.sp_executesql使用三部分名称调用,则将上下文设置为调用它的数据库.

因此,您可以使用 SQL注入风险执行此操作,如下所示.

CREATE PROCEDURE dbo.test @dbname SYSNAME,
                          @col    SYSNAME
AS
    SET NOCOUNT, XACT_ABORT ON;

    DECLARE @db_sp_executesql NVARCHAR(300) = QUOTENAME(@dbname) + '.sys.sp_executesql'

    EXEC @db_sp_executesql N'
                            SELECT TOP 100 *
                            FROM sys.columns 
                            WHERE name = @col',
                           N'@col sysname',
                           @col = @col 
Run Code Online (Sandbox Code Playgroud)

即使上述情况不可能,我仍然认为完全可以在这里以安全的方式使用动态SQL.

CREATE PROCEDURE dbo.test
    @dbname SYSNAME, /*Use Correct Datatypes for identifiers*/
    @col SYSNAME
AS
    SET NOCOUNT ON
    SET XACT_ABORT ON

    IF DB_ID(@dbname) IS NULL  /*Validate the database name exists*/
       BEGIN
       RAISERROR('Invalid Database Name passed',16,1)
       RETURN
       END

DECLARE @dynsql nvarchar(max)  

 /*Use QUOTENAME to correctly escape any special characters*/
SET @dynsql = N'USE '+ QUOTENAME(@dbname) + N'

                         SELECT TOP 100 *
                         FROM sys.tables 
                         WHERE name = @col'

 /*Use sp_executesql to leave the WHERE clause parameterised*/
EXEC sp_executesql @dynsql, N'@col sysname', @col = @col
Run Code Online (Sandbox Code Playgroud)

  • 非常好.我没想过用`db_id()`来验证`@dbname`参数. (3认同)
  • 应该如何处理跨多个数据库的表之间的连接。 (2认同)

3Da*_*ave 16

至少有两种方法可以做到这一点:

  1. 使用case/switch语句(或者,在我的示例中,一个朴素的if..else块)将参数与数据库列表进行比较,并基于此执行using语句.这具有限制proc可以访问已知集的数据库的优点,而不是允许访问用户帐户有权访问的任何内容和所有内容.

    declare @dbname nvarchar(255);    
    set @dbname = 'db1';    
    if @dbname = 'db1'
     use db1;
    else if @dbname = 'db2'
     use db2;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 动态SQL.我讨厌动态SQL.这是一个巨大的安全漏洞,几乎没有必要.(从正确的角度来看:在17年的专业发展中,我从未必须部署使用动态SQL的生产系统).如果您决定使用此路由,请将动态调用/创建的代码限制为using语句,并调用另一个存储过程执行实际工作.using由于范围规则,您不能仅自动动态执行语句.

    declare @sql nvarchar(255);
    set @sql = 'using '+@dbname+'; exec mydatabase..do_work_proc;';
    
    Run Code Online (Sandbox Code Playgroud)

当然,在你的例子中,你可以做到

    set @sql='select * from '+@dbname+'.sys.tables';
Run Code Online (Sandbox Code Playgroud)

.<schema_name>.解析运算符可以查询在不同数据库中的对象,而无需使用use声明.

在一些非常非常罕见的情况下,可能需要允许sproc使用任意数据库.在我看来,唯一可接受的用途是代码生成器,或某种数据库分析工具,它不能提前知道所需的信息.

更新原来你不能use在存储过程中,将动态SQL作为唯一明显的方法.不过,我考虑使用

select top 100 * from db_name.dbo.table_name
Run Code Online (Sandbox Code Playgroud)

而不是一个use.

  • 除非在SQL Server明确禁止存储过程中如何使用`USE <database>`命令? (7认同)