我正在尝试创建一个查询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的参数?
答案 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)
至少有两种方法可以做到这一点:
使用case / switch语句(或者,在我的示例中,一个朴素的if..else
块)将参数与数据库列表进行比较,并根据该语句执行using语句。这样做的好处是可以限制proc可以访问已知集合的数据库,而不是允许访问用户帐户有权访问的任何内容和所有内容。
declare @dbname nvarchar(255);
set @dbname = 'db1';
if @dbname = 'db1'
use db1;
else if @dbname = 'db2'
use db2;
动态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
语句的情况下查询其他数据库中的对象。
在某些非常非常罕见的情况下,可能需要允许sproc使用任意数据库。在我看来,唯一可接受的用途是代码生成器,或某种数据库分析工具,它不能提前知道所需的信息。
更新原来你不能在存储过程中use
,将动态SQL留作唯一明显的方法。不过,我还是考虑使用
select top 100 * from db_name.dbo.table_name
而不是use
。
答案 2 :(得分:1)
这样做的唯一方法是使用Dynamic SQL,这是强大但危险的。
答案 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,则可以选择它。