在 SQL Server 数据库之间传递用户定义的表类型
Posted
技术标签:
【中文标题】在 SQL Server 数据库之间传递用户定义的表类型【英文标题】:Passing a user-defined table type between SQL Server database 【发布时间】:2013-01-24 15:21:16 【问题描述】:我在 SQL Server 的一个数据库中有一个用户定义的表类型(我们称之为DB1
)。
我的类型的定义非常简单,只包含 2 列。创建我的类型的脚本如下:
CREATE TYPE [dbo].[CustomList] AS TABLE
(
[ID] [int] ,
[Display] [NVARCHAR] (100)
)
我还在另一个数据库上运行了相同的脚本,所以我的类型是在 2 个数据库上(我们称第二个数据库为DB2
)。
我现在从我的 C# 应用程序中调用 DB1
中的存储过程,并传入我的 CustomList
用户定义类型的参数。
DB1
中的过程现在需要调用 DB2
上的过程,传入此 CustomList
。
所以,DB1
中的过程如下所示:
ALTER PROCEDURE [dbo].[selectData]
@psCustomList CustomList ReadOnly
AS
BEGIN
EXEC DB2.dbo.selectMoreData @psCustomList
END
DB2
中的过程是这样的(我只显示了参数列表,因为这就是所有需要的):
ALTER PROCEDURE [dbo].[selectMoreData]
@psCustomList CustomList ReadOnly
AS
BEGIN
......
当我运行它时,我收到以下错误:
操作数类型冲突:CustomList 与 CustomList 不兼容
有人知道我做错了什么吗?
我使用的是 SQL Server 2008。
提前致谢
【问题讨论】:
Passing Table Valued parameter to stored procedure across different databases的可能重复 “所以我的类型在 2 个数据库上” - 不,您的两个数据库恰好有定义为相同名称和结构的表类型。没有它们是同一类型的概念。而且您无法定义不同数据库类型的变量,因此您甚至无法创建正确类型的变量并复制数据。 【参考方案1】:这是Can you create a CLR UDT to allow for a shared Table type across databases?的副本
本质上,用户定义的表类型不能跨数据库共享。基于 CLR 的 UDT 可以跨数据库共享,但前提是满足某些条件,例如将相同的程序集加载到两个数据库中,以及其他一些事情(详细信息在重复的问题中注明以上)。
对于这种特殊情况,有一种方法可以将信息从DB1
传递到DB2
,尽管这不是一个优雅的解决方案。为了使用表类型,您当前的数据库上下文需要是表类型所在的数据库。这是通过USE
语句完成的,但如果需要在存储过程中完成,则只能在动态 SQL 中完成。
USE [DB1];
GO
CREATE PROCEDURE [dbo].[selectData]
@psCustomList CustomList READONLY
AS
BEGIN
-- create a temp table as it can be referenced in dynamic SQL
CREATE TABLE #TempCustomList
(
[ID] [INT],
[Display] [NVARCHAR] (100)
);
INSERT INTO #TempCustomList (ID, Display)
SELECT ID, Display FROM @psCustomList;
EXEC('
USE [DB2];
DECLARE @VarCustomList CustomList;
INSERT INTO @VarCustomList (ID, Display)
SELECT ID, Display FROM #TempCustomList;
EXEC dbo.selectMoreData @VarCustomList;
');
END
更新
使用sp_executesql
试图通过简单地将UDTT 作为TVP 传递来避免本地临时表,或者仅仅作为进行参数化查询的一种方式,实际上都不起作用(尽管它看起来确实像应该)。含义,如下:
USE [DB1];
GO
CREATE PROCEDURE dbo.CrossDatabaseTableTypeA
(
@TheUDTT dbo.TestTable1 READONLY
)
AS
SET NOCOUNT ON;
EXEC sp_executesql N'
USE [DB2];
SELECT DB_NAME() AS [CurrentDB];
DECLARE @TableTypeDB2 dbo.TestTable2;
INSERT INTO @TableTypeDB2 ([Col1])
SELECT tmp.[Col1]
FROM @TableTypeDB1 tmp;
--EXEC dbo.CrossDatabaseTableTypeB @TableTypeDB2;
',
N'@TableTypeDB1 dbo.TestTable1 READONLY',
@TableTypeDB1 = @TheUDTT;
GO
DECLARE @tmp dbo.TestTable1;
INSERT INTO @tmp ([Col1]) VALUES (1), (3);
SELECT * FROM @tmp;
EXEC dbo.CrossDatabaseTableTypeA @TheUDTT = @tmp;
将在“@TableTypeDB2 的数据类型无效”上失败,即使它正确显示 DB2
是“当前”数据库。它与sp_executesql
如何确定变量数据类型有关,因为错误将@TableTypeDB2
称为“变量#2”,即使它是在本地创建的而不是作为输入参数。
事实上,sp_executesql
会在声明单个变量时出错(通过sp_executesql
的参数列表输入参数),即使它从未被引用,更不用说使用了。这意味着,以下代码将遇到与上面的查询发生的无法找到 UDTT 定义的相同错误:
USE [DB1];
GO
CREATE PROCEDURE dbo.CrossDatabaseTableTypeC
AS
SET NOCOUNT ON;
EXEC sp_executesql N'
USE [DB2];
SELECT DB_NAME() AS [CurrentDB];
DECLARE @TableTypeDB2 dbo.TestTable2;
',
N'@SomeVar INT',
@SomeVar = 1;
GO
(感谢@Mark Sowul 提到sp_executesql
在传递变量时不起作用)
但是,这个问题可以通过更改sp_executesql
的执行数据库来解决(好吧,只要您不尝试传入 TVP 以避免临时表 - 上面的 2 个查询)这样该过程将在另一个 TVP 所在的数据库中是本地的。关于sp_executesql
的一个好处是,与EXEC
不同,它是一个存储过程,并且是一个系统存储过程,因此它可以是完全限定的。利用这一事实允许sp_executesql
工作,这也意味着动态SQL 中不需要USE [DB2];
语句。以下代码确实有效:
USE [DB1];
GO
CREATE PROCEDURE dbo.CrossDatabaseTableTypeD
(
@TheUDTT dbo.TestTable1 READONLY
)
AS
SET NOCOUNT ON;
-- create a temp table as it can be referenced in dynamic SQL
CREATE TABLE #TempList
(
[ID] [INT]
);
INSERT INTO #TempList ([ID])
SELECT [Col1] FROM @TheUDTT;
EXEC [DB2].[dbo].sp_executesql N'
SELECT DB_NAME() AS [CurrentDB];
DECLARE @TableTypeDB2 dbo.TestTable2;
INSERT INTO @TableTypeDB2 ([Col1])
SELECT tmp.[ID]
FROM #TempList tmp;
EXEC dbo.CrossDatabaseTableTypeB @TableTypeDB2;
',
N'@SomeVariable INT',
@SomeVariable = 1111;
GO
【讨论】:
请注意,如果您使用sp_executesql
,如果您尝试使用参数块显然将无法正常工作
@MarkSowul 我测试了,看看你的意思。我更新以添加该信息。谢谢!
不完全——在sp_executesql
中使用 any 参数就可以了。我还在使用临时表;我只是传递了一些无害的东西。然后你会得到奇怪的错误inside动态SQL,抱怨表类型的使用(即使你用USE
切换了dbs)
@MarkSowul 是的,我现在明白了。事实上,简单地声明一个变量将导致在“当前”数据库中搜索 UDTT 定义。但是,我在更新的答案中显示了一个修复:-)以上是关于在 SQL Server 数据库之间传递用户定义的表类型的主要内容,如果未能解决你的问题,请参考以下文章
使用 JDBC 将用户定义的表类型传递给 SQL Server 存储过程
将用户定义表中的日期值传递给 SQL Server 2008 存储过程
重新定义 MS Access 文件和 SQL Server 之间的数据链接