我在SQL Server的一个数据库中有一个用户定义的表类型(让我们称之为DB1
)。
我的类型的定义非常简单,只包含2列。创建我的类型的脚本如下:
CREATE TYPE [dbo].[CustomList] AS TABLE
(
[ID] [int] ,
[Display] [NVARCHAR] (100)
)
我也在另一个数据库上运行相同的脚本,所以我的类型在2个数据库上(让我们调用第二个数据库DB2
)。
我现在从我的C#app中调用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。
提前致谢
答案 0 :(得分:9)
这是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
在传入变量时不起作用)
然而,这个问题可以通过改变{{1的执行数据库来解决这个问题(好吧,只要你不试图传入TVP以避免临时表 - 上面的2个查询)这样,进程将是其他TVP所在的DB的本地进程。关于sp_executesql
的一个好处是,与sp_executesql
不同,它是一个存储过程,并且是一个系统存储过程,因此它可以是完全限定的。利用这一事实可以使EXEC
工作,这也意味着动态SQL中不需要sp_executesql
语句。以下代码确实有效:
USE [DB2];