【问题标题】:SQL Server: how to get a database name as a parameter in a stored procedureSQL Server:如何在存储过程中获取数据库名称作为参数
【发布时间】:2010-10-15 15:46:00
【问题描述】:

我正在尝试创建一个查询 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 但这会终止此过程的创建?如何将此数据库选择放入此过程中,以便用户可以将数据库名称作为此过程的参数?

【问题讨论】:

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


    【解决方案1】:

    如果你使用EXEC @Var(不带括号 - 即not 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
    

    【讨论】:

    • 非常好。我没有想过使用db_id() 来验证@dbname 参数。
    • 应该如何处理跨多个数据库的表之间的连接。
    【解决方案2】:

    编辑

    我的回答假设了一些使这种方法实际上毫无用处的东西。不幸的是,SO不会让我删除答案。我推荐@MartinSmith 的答案(在此线程下方)。我认为这里仍然有一些有用的信息,但它实际上并没有解决最初的问题。神速。

    原始回复

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

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

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

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

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

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

    更新 事实证明您不能在存储过程中使用use,而将动态 SQL 作为唯一明显的方法。不过,我会考虑使用

    select top 100 * from db_name.dbo.table_name
    

    而不是use。

    【讨论】:

    • SQL Server 明确禁止在存储过程中使用USE <database> 命令除外?
    • 哎呀——是的,SQL Server 不允许在存储过程中使用use。答案已更新。
    【解决方案3】:

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

    Read this article first.

    【讨论】:

      【解决方案4】:

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

      见SQL Stored Procedure(s) - Execution From Multiple Databases。

      如果过程名称以“sp_”开头,在主数据库中并用 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;
      

      也可以使用参数。

      使用此技术需要使用“sp_”前缀并将代码放入系统数据库。如果不使用动态 SQL 进行偏移,这是您的选择。

      【讨论】:

        【解决方案5】:

        或使用 Powershell 运行脚本并传递 dbname

            # Script #1 - clear the existing CLR functions, sps, and assemblies 
            $scriptFile = "Script_Drop_Functions_and_Other_Programmability_Objects.sql";
        
            # Standard PARAMETERS to the PS cmdlet Invoke-Sqlcmd - these are splatted here to standardize.  This PARAMETERS includes the standard flags e.g. ServerInstance as well as the variables for each script
            $sqlcmdParameters = @{
                ServerInstance = $serverName #cmd flag
                Database       = $dbName #cmd flag
                InputFile      = $scriptFile #cmd flag, changes for each invocation
                Variable       = $sqlCmdVariables #cmd flag, changes for each invocation
                Verbose        = $true #cmd flag, set to true by default for additional output detail
            };
        
            <# PowerShell numbers the types of 'stream' messages according to their type from 1 to 6 - Use these numbers in order to control the returned output: 
            (1) success message, which is the output of the success command execution. This corresponds to SQL Server query results
            (2) Error messages. 
            (3) Warning messages. 
            (4) Verbose messages. This corresponds to SQL Server PRINT statements
            (5) Debug massages. 
            (6) Information massages.  
        
            Type 1 will be put in file $logFileResults 
            Type 4  will be put in file $logFileOutput  
            
            #>
        
            (Invoke-Sqlcmd @sqlcmdParameters >> $logFileOutput)  4>> $logFileResults 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-23
          • 2015-05-26
          • 1970-01-01
          • 2016-09-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多