[英]Dynamic SQL without having to use fully qualified table names in SQL (Openrowset?)
我有大量的预先存在的SQL select语句。
从[Server_A]上的存储过程中,我想在多个不同的SQL Servers和数据库上执行这些语句中的每一个(列表存储在[Server_A]上的本地表中,并将结果返回到[Server_A]上的表中) 。
但是,我不想在我的sql语句中使用完全限定的表名。 我要执行“从用户选择*”,而不是“从ServerName.DatabaseName.SchemaName.Users选择*”
我使用Openrowset进行了调查,但是找不到任何可以将Server name和DatabaseName都指定为连接属性的示例,而不是实际嵌入到实际SQL语句中的示例。
Openrowset有能力做到这一点吗? 是否有替代方法(从存储过程中而不是通过Powershell或其他非常不同的方法来完成此操作)?
不可避免的“我为什么要这样做?”
通过SQLCLR可以很容易地做到这一点。 如果结果集是动态的,则它必须是存储过程而不是TVF。
假设您正在执行存储过程,则只需:
@ServerName, @DatabaseName, @SQL
SqlConnection
: String.Concat("Server=", ServerName.Value, "; Database=", DatabaseName.Value, "; Trusted_Connection=yes; Enlist=false;")
或使用ConnectionStringBuilder
SqlConnection
创建一个SqlCommand
并使用SQL.Value
。 SqlContext.WindowsIdentity.Impersonate();
启用模拟SqlContext.WindowsIdentity.Impersonate();
_Connection.Open();
_Reader = Command.ExecuteReader();
SqlContext.Pipe.Send(_Reader);
finally
子句中处理Reader,Command,Connection和ImpersonationContext 与启用临时分布式查询访问相比,此方法更安全和可控,因此它比安全问题少。 它还不允许SQL Server登录名获得提升的权限,因为当代码执行Impersonate()
方法时,SQL Server登录名将获得错误。
另外,这种方法允许返回多个结果集,而OPENROWSET不允许这样做:
尽管查询可能返回多个结果集,但OPENROWSET仅返回第一个结果集。
UPDATE
修改后的伪代码基于对此答案的评论:
@QueryID
SqlConnection
(_MetaDataConnection),其连接字符串为: Context Connection = true;
SqlDataReader
查询_MetaDataConnection以获取ServerName
, DatabaseName
和基于QueryID.Value
Query
SqlConnection
(_QueryConnection): String.Concat("Server=", _Reader["ServerName"].Value, "; Database=", _Reader["DatabaseName"].Value, "; Trusted_Connection=yes; Enlist=false;")
或使用ConnectionStringBuilder
_Reader["SQL"].Value
为_QueryConnection创建SqlCommand
(_QueryCommand)。 QueryID.Value
获取参数名称和值 SqlDataReader
以创建SqlParameter
并添加到_QueryCommand _MetaDataConnection.Close();
SqlContext.WindowsIdentity.Impersonate();
启用模拟SqlContext.WindowsIdentity.Impersonate();
_QueryConnection.Open();
_Reader = _QueryCommand.ExecuteReader();
SqlContext.Pipe.Send(_Reader);
finally
子句中处理读取器,命令,连接和ImpersonationContext 如果要在实例中的每个数据库上执行sql语句,则可以使用exec sp_MSforeachdb
(不受支持,非官方但广泛使用的),如下所示:
EXEC sp_Msforeachdb 'use [?]; select * from users'
这相当于通过一个数据库遍历每个数据库。
use db...
go
select * from users
这是一个有趣的问题,因为我搜索了许多小时,并且发现有许多人试图做与问题所要求的完全相同的事情。
最常见的回应:
幸运的是,我偶然发现了答案,而且很简单。 我认为问题的部分原因是,使用不同的提供程序和连接字符串时,它的变化太多,而且可能出错的事物太多,而当发生错误时,错误消息通常并不会给人以启发。
无论如何,这是您的操作方法:
如果使用静态SQL:
select * from OPENROWSET('SQLNCLI','Server=ServerName[\InstanceName];Database=AdventureWorks2012;Trusted_Connection=yes','select top 10 * from HumanResources.Department')
如果您使用的是Dynamic SQL,则由于OPENROWSET不接受变量作为参数,因此可以使用以下方法(仅作为一个人为的示例):
declare @sql nvarchar(4000) = N'select * from OPENROWSET(''SQLNCLI'',''Server=Server=ServerName[\InstanceName];Database=AdventureWorks2012;Trusted_Connection=yes'',''@zzz'')'
set @sql = replace(@sql,'@zzz','select top 10 * from HumanResources.Department')
EXEC sp_executesql @sql
值得注意的是:如果您认为将此语法包装在一个接受@ ServerName,@ DatabaseName,@ SQL的漂亮的表值函数中会很好-您不能这样做,因为TVF的结果集列必须在编译时确定。
相关阅读:
结论:
OPENROWSET是唯一可以100%避免至少完全限定对象名称的方法; 即使使用EXEC AT,您仍然必须在对象前面加上数据库名称。
额外提示:普遍的意见似乎是“因为存在安全隐患”(不包含任何隐患),不应使用OPENROWSET。 我的理解是,这种风险仅在您使用SQL Server身份验证的情况下进行,此处有更多详细信息:
https://technet.microsoft.com/en-us/library/ms187873%28v=sql.90%29.aspx?f=255&MSPPError=-2147217396
连接到另一个数据源时,SQL Server会适当地模拟Windows身份验证登录名的登录名。 但是,SQL Server无法模拟SQL Server身份验证的登录名。 因此,对于经过SQL Server身份验证的登录,SQL Server可以使用运行SQL Server服务的Windows帐户的安全上下文来访问另一个数据源,例如文件,非关系数据源(例如Active Directory)。 这样做可能使此类登录名可以访问他们没有权限的另一个数据源,但是运行SQL Server服务的帐户确实具有权限。 使用SQL Server身份验证登录名时,应考虑这种可能性。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.