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

时间:2010-10-15 15:46:01

标签: sql sql-server sql-server-2008 stored-procedures

我正在尝试创建一个查询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

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

4 个答案:

答案 0 :(得分: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 

即使上述情况不可能,我仍然认为完全可以在这里以安全的方式使用动态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

答案 1 :(得分: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;
    
  2. 动态SQL。 我讨厌动态SQL。这是一个巨大的安全漏洞,几乎没有必要。 (从正确的角度来看:在17年的专业开发中,我从未必须部署使用动态SQL的生产系统)。如果您决定使用此路由,请将动态调用/创建的代码限制为using语句,并调用另一个存储过程执行实际工作。由于范围规则,您不能仅自动动态执行using语句。

    declare @sql nvarchar(255);
    set @sql = 'using '+@dbname+'; exec mydatabase..do_work_proc;';
    
  3. 当然,在你的例子中,你可以做到

        set @sql='select * from '+@dbname+'.sys.tables';
    

    .<schema_name>.解析运算符允许您在不使用use语句的情况下查询其他数据库中的对象。

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

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

    select top 100 * from db_name.dbo.table_name
    

    而不是use

答案 2 :(得分:1)

这样做的唯一方法是使用Dynamic SQL,这是强大但危险的。

Read this article first.

答案 3 :(得分:0)

同一目的的另一种方法是使用系统存储过程。

请参阅SQL Stored Procedure(s) - Execution From Multiple Databases

如果过程名称以&#34; sp _&#34;开头,在主数据库中并标有sys.sp_MS_MarkSystemObject,则可以这样调用:

Exec somedb.dbo.Test;
Exec anotherdb.dbo.Test;

或者像这样:

Declare @Proc_Name sysname;
Set @Proc_Name = 'somedb.dbo.Test';
Exec @Proc_Name;

也可以使用参数。

使用此技术需要使用&#39; sp _&#39;前缀并将代码放入系统数据库。如果不使用动态SQL,则可以选择它。