2010-10-15 58 views
14

我试图创建一个查询sys.tables表的简单存储过程。SQL Server:如何获取数据库名称作为存储过程中的参数

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 

这似乎不是工作原因,我应该使用@dbname后把GO但这终止该程序的创造?我怎样才能把这个数据库选择放到这个过程中,以便用户可以给出一个数据库名称作为这个过程的参数?

回答

15

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

  1. 使用情况/ switch语句(或者,在我的例子,一个天真if..else块)的参数比较反对数据库列表,并基于此执行使用语句。这具有限制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 '[email protected]+'; exec mydatabase..do_work_proc;'; 
    
当然

,在你的榜样,你可以只是做

set @sql='select * from '[email protected]+'.sys.tables'; 

.<schema_name>.分辨率运营商可以查询在不同数据库中的对象,而无需使用use声明。

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

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

select top 100 * from db_name.dbo.table_name 

而不是use

+6

除非在SQL Server明确禁止它的情况下,如何在存储过程中使用'USE '命令? – JNK 2010-10-15 17:02:52

+0

感谢您的信息! – jjoras 2010-10-15 17:27:22

+0

Whoops - yep,SQL Server不允许在存储过程中使用。答案已更新。 – 2010-10-16 17:18:05

25

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

非常好。我没有想过用'db_id()'来验证'@ dbname'参数。 – 2010-10-16 17:17:02

+0

应该如何处理跨多个数据库的表之间的连接处理。 – akashchandrakar 2016-03-29 11:33:38

0

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

参见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,则是您的选择。

相关问题