[英]SQL Server: How to use a database name as a parameter in a stored procedure?
[英]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 的答案(在此线程下方)。 我认为这里仍然有一些有用的信息,但它实际上并没有解决最初的问题。 神速。
至少有两种方法可以做到这一点:
使用 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
语句的情况下查询不同数据库中的对象。
在一些非常非常罕见的情况下,可能需要允许存储过程使用任意数据库。 在我看来,唯一可接受的用途是代码生成器,或某种无法提前知道所需信息的数据库分析工具。
更新结果你不能在存储过程中use
,动态 SQL 作为唯一明显的方法。 不过,我会考虑使用
select top 100 * from db_name.dbo.table_name
而不是一个use
。
达到同一目的的另一种方法是使用系统存储过程。
如果过程名称以“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.