繁体   English   中英

SQL Server:如何在存储过程中获取数据库名称作为参数

[英]SQL Server: how to get a database name as a parameter in a stored procedure

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

如果您使用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

编辑

我的回答假设了一些使这种方法实际上毫无用处的东西。 不幸的是,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 ,它功能强大但很危险。

首先阅读这篇文章。

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

请参阅SQL 存储过程 - 从多个数据库执行

如果过程名称以“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 进行偏移,这是您的选择。

或使用 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 

以下是一个聪明的HACK ,它完全符合 OP 的要求。 我们在内部使用类似这样的东西,它允许 QA 测试人员将数据从任意数量的客户端数据库列表中提取到存储过程实际所在的模拟测试数据库中。

为了演示这个例子,我在从我的服务器访问的几个数据库上执行了以下操作,只是为了创建一个表来测试

select * into MyTestTable from sys.all_columns

然后,我在目标数据库上创建了以下存储过程。 我评论了它,我认为足以让某人了解正在发生的事情。 我不会争论这种方法的优点和/或危险。 有用。 它不需要将所有代码转储到动态 SQL 字符串等中。

create procedure AnyDbStoredProcedure
    @dbname varchar(200),
    @count int
as
    -- You can add additional parameters as needed above.
    -- Modify the call to sp_executesql at the bottom of the 
    -- stored procedure as needed to include or exclude parameters.
    set nocount on

    -- MARK THE START OF THE STORED PROCEDURE CODE YOU 
    -- ACTUALLY WANT TO EXECUTE WITH A COMMENTED GUID.
    -- (SEE BELOW)
    declare @guid varchar(38) = '-- {5E105697-D144-4073-A1A6-330A264159DF}'

    -- You can call your stored procedure what you like, 
    -- but its name must be copied to the parameter here.
    declare @sp_name nvarchar(200) = 'AnyDbStoredProcedure'
                                
    -- EVERY DATABASE OBJECT YOUR SCRIPT REFERENCES MUST 
    -- INCLUDE THE FULLY QUALIFIED DATABASE NAME.
    -- WHATEVER DATABASE YOU USED, PUT ITS NAME HERE:
    declare @dbname_to_replace varchar(20) = '[FloridaDev]'

    declare @spbody table ( 
        line_num int not null identity(1,1), 
        [Text] varchar(max))
    declare @sql nvarchar(max) = '', @params_sql nvarchar(max) = ''
    declare @text varchar(max), @sp_body_started int = 0, @in_params bit = 0
    
    insert @spbody ([Text])
    
    exec sp_helptext @sp_name

    declare csr cursor fast_forward for
    select [Text] from @spbody 
    order by line_num

    open csr
    fetch next from csr into @text
    while @@FETCH_STATUS = 0
    begin
        if LEFT(@text, 16) = 'create procedure'
        begin
            set @in_params = 1
        end
        else if @text = 'as' + char(13) + char(10) and @in_params = 1
        begin
            set @in_params = 0
        end
        else if @in_params = 1
        begin
            set @params_sql = @params_sql + @text
        end
        else if CHARINDEX(@guid, @text) > 0 
        begin
            -- Ignoring the first instance found which is in 
            -- the variable declaration above.
            set @sp_body_started = @sp_body_started + 1
        end
        else if @sp_body_started = 2
        begin
            set @text = REPLACE(@text, @dbname_to_replace, @dbname)
            set @sql = @sql + @text
        end

        fetch next from csr into @text
    end

    close csr
    deallocate csr
    set nocount off
    
    print '== PARAMS =='
    print @params_sql
    print '============'
    print @sql
    -- BE SURE TO PASS YOUR ADDITIONAL PARAMETERS IF YOU HAVE ANY.
    exec sp_executesql @sql, @params_sql, 'dummy_for_@dbname', @count
    
    return

    -- AS MENTIONED AT THE TOP, MARK THE START OF THE STORED PROCEDURE  
    -- CODE YOU ACTUALLY WANT TO EXECUTE WITH A COMMENTED GUID.
    -- {5E105697-D144-4073-A1A6-330A264159DF}
    select object_name(object_id), name
    from (
        select *, row = row_number() over (order by object_id, column_id)
        from [FloridaDev].dbo.MyTestTable
    ) as tmp
    where tmp.row <= @count -- EXAMPLE USES EXTRA PARAMETER
go


exec AnyDbStoredProcedure 'Florida', 20

暂无
暂无

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

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