9

我在 SQL Server 的一个数据库中有一个用户定义的表类型(我们称之为这个DB1)。

我的类型的定义非常简单,只包含 2 列。创建我的类型的脚本如下:

CREATE TYPE [dbo].[CustomList] AS TABLE
(
    [ID] [int] ,
    [Display] [NVARCHAR] (100)  
)

我还在另一个数据库上运行了相同的脚本,所以我的类型在 2 个数据库上(我们称之为 2nd database DB2)。

我现在DB1从我的 C# 应用程序中调用一个存储过程,传入我的CustomList用户定义类型的参数。

现在的过程需要在传入 thisDB1时调用一个过程。DB2CustomList

因此,中的过程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。

提前致谢

4

1 回答 1

11

这是您可以创建 CLR UDT 以允许跨数据库共享表类型的副本吗?

本质上,用户定义的表类型不能跨数据库共享。基于 CLR 的 UDT可以跨数据库共享,但前提是满足某些条件,例如将相同的程序集加载到两个数据库中,以及其他一些事情(详细信息在上面提到的重复问题中)。

对于这种特殊情况,有一种方法可以将信息从DB1to传递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如果声明了单个变量(通过参数列表输入参数 to 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在传递变量时不起作用)

但是,可以通过更改执行数据库来解决此问题(好吧,只要您不尝试传入 TVP 以避免临时表 - 上面的 2 个查询),sp_executesql以便该过程将位于另一个 TVP 所在的数据库的本地。关于它的一个好处sp_executesql是,与 不同的是EXEC,它是一个存储过程,并且是一个系统存储过程,因此它可以是完全限定的。利用这一事实允许sp_executesql工作,这也意味着不需要USE [DB2];动态 SQL 中的语句。以下代码确实有效:

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
于 2014-06-25T04:59:02.170 回答