在SQL Server数据库之间传递用户定义的表类型

时间:2013-01-24 15:21:17

标签: sql-server sql-server-2008 user-defined-types

我在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。

提前致谢

1 个答案:

答案 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];