动态SQL而不必在SQL中使用完全限定的表名(Openrowset?)

时间:2015-03-02 23:31:26

标签: sql-server tsql

我有一大堆预先存在的sql select语句。

从[Server_A]上的存储过程,我想在多个不同的SQL Server&上执行这些语句。数据库(列表存储在[Server_A]上的本地表中,并将结果返回到[Server_A]上的表中。

但是,我不想在我的sql语句中使用完全限定的表名。我想执行" select * from users",not" select * from ServerName.DatabaseName.SchemaName.Users"

我已经使用Openrowset进行了调查,但是我找不到任何可以将服务器名称和DatabaseName都指定为作为连接属性的示例,而不是实际嵌入到实际的SQL语句。

Openrowset能胜任吗?是否有另一种方法(从存储过程中,而不是诉诸Powershell或其他一些非常不同的方法?)

不可避免的"为什么我要这样做?"

  • 您可以这样做(在连接中指定服务器和数据库) 属性,然后在所有数据库中使用完全通用的sql) 几乎所有其他语言访问SQL Server。
  • 将所有预先存在的复杂SQL更改为完全限定的是a 巨大的PITA(此外,你根本不应该这样做)

3 个答案:

答案 0 :(得分:1)

这可以通过SQLCLR轻松完成。如果结果集是动态的,那么它需要是存储过程而不是TVF。

假设您正在执行存储过程,您只需:

  • 传入@ServerName, @DatabaseName, @SQL
  • 创建一个SqlConnection,其连接字符串为:String.Concat("Server=", ServerName.Value, "; Database=", DatabaseName.Value, "; Trusted_Connection=yes; Enlist=false;")或使用ConnectionStringBuilder
  • SqlCommand创建SqlConnection并使用SQL.Value
  • 通过SqlContext.WindowsIdentity.Impersonate();
  • 启用模拟
  • _Connection.Open();
  • 撤消模拟 - 仅需要建立连接
  • _Reader = Command.ExecuteReader();
  • SqlContext.Pipe.Send(_Reader);
  • finally子句中处理Reader,Command,Connection和ImpersonationContext

与支持Ad Hoc Distributed Query访问相比,这种方法不是一个安全问题,因为它更加绝缘和可控。它还不允许SQL Server登录获得提升权限,因为当代码执行Impersonate()方法时,SQL Server登录将收到错误。

此外,此方法允许返回多个结果集,OPENROWSET不允许的内容:

  

虽然查询可能返回多个结果集,但OPENROWSET只返回第一个结果集。


<强>更新

基于对此答案的评论修改的伪代码:

  • 传入@QueryID
  • 创建一个SqlConnection(_MetaDataConnection),其连接字符串为:Context Connection = true;
  • 通过ServerName
  • 根据DatabaseName查询_MetaDataConnection以获取QueryQueryID.ValueSqlDataReader
  • 创建另一个SqlConnection(_QueryConnection),其连接字符串为:String.Concat("Server=", _Reader["ServerName"].Value, "; Database=", _Reader["DatabaseName"].Value, "; Trusted_Connection=yes; Enlist=false;")或使用ConnectionStringBuilder
  • 使用SqlCommand为_QueryConnection创建_Reader["SQL"].Value(_ QueryCommand)。
  • 使用_MetaDataConnection,查询以获取基于QueryID.Value
  • 的参数名称和值
  • 循环浏览SqlDataReader以创建SqlParameter并添加到_QueryCommand
  • _MetaDataConnection.Close();
  • 通过SqlContext.WindowsIdentity.Impersonate();
  • 启用模拟
  • _QueryConnection.Open();
  • 撤消模拟 - 仅需要建立连接
  • _Reader = _QueryCommand.ExecuteReader();
  • SqlContext.Pipe.Send(_Reader);
  • finally子句中处理读者,命令,连接和ImpersonationContext

答案 1 :(得分:0)

如果要在实例中的每个数据库上执行sql语句,可以使用(不支持的,非官方的,但广泛使用的)exec sp_MSforeachdb,如下所示:

EXEC sp_Msforeachdb 'use [?];  select * from users'

这相当于通过

遍历每个数据库
use db...
go
select * from users

答案 2 :(得分:0)

这是一个有趣的问题,因为我搜索了很多很多小时,并且发现有几个人试图做与问题中提到的完全相同的事情。

最常见的回复:

  • 你为什么要那样做?
  • 不能这样做,必须完全限定您的对象名称
幸运的是,我偶然发现了答案,这很简单。我认为问题的一部分是,它与不同的提供商和它有很多变化。连接字符串,并且有很多东西可能出错,当有人这样做时,错误信息往往不是非常有启发性。

无论如何,这是你如何做到的:

如果您使用的是静态SQL:

select * from OPENROWSET('SQLNCLI','Server=ServerName[\InstanceName];Database=AdventureWorks2012;Trusted_Connection=yes','select top 10 * from HumanResources.Department')

如果您正在使用动态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的很好的Table Valued函数中会很好,你就不能,因为TVF的结果集列必须在编译时确定。

相关阅读:

http://blogs.technet.com/b/wardpond/archive/2005/08/01/the-openrowset-trick-accessing-stored-procedure-output-in-a-select-statement.aspx

http://blogs.technet.com/b/wardpond/archive/2009/03/20/database-programming-the-openrowset-trick-revisited.aspx

<强>结论:
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身份验证登录时,应考虑这种可能性。